├── .github
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug_report.yml
│ ├── config.yml
│ └── feature_request.yml
├── PULL_REQUEST_TEMPLATE.md
├── SECURITY.md
├── dependabot.yml
└── workflows
│ ├── codeql-analysis.yml
│ └── go.yml
├── .gitignore
├── LICENSE
├── README.md
├── README_zh.md
├── adjust.go
├── adjust_test.go
├── calc.go
├── calc_test.go
├── calcchain.go
├── calcchain_test.go
├── cell.go
├── cell_test.go
├── chart.go
├── chart_test.go
├── col.go
├── col_test.go
├── crypt.go
├── crypt_test.go
├── datavalidation.go
├── datavalidation_test.go
├── date.go
├── date_test.go
├── docProps.go
├── docProps_test.go
├── drawing.go
├── drawing_test.go
├── errors.go
├── errors_test.go
├── excelize.go
├── excelize.svg
├── excelize_test.go
├── file.go
├── file_test.go
├── go.mod
├── go.sum
├── hsl.go
├── lib.go
├── lib_test.go
├── logo.png
├── merge.go
├── merge_test.go
├── numfmt.go
├── numfmt_test.go
├── picture.go
├── picture_test.go
├── pivotTable.go
├── pivotTable_test.go
├── rows.go
├── rows_test.go
├── shape.go
├── shape_test.go
├── sheet.go
├── sheet_test.go
├── sheetpr.go
├── sheetpr_test.go
├── sheetview.go
├── sheetview_test.go
├── slicer.go
├── slicer_test.go
├── sparkline.go
├── sparkline_test.go
├── stream.go
├── stream_test.go
├── styles.go
├── styles_test.go
├── table.go
├── table_test.go
├── templates.go
├── test
├── BadWorkbook.xlsx
├── Book1.xlsx
├── CalcChain.xlsx
├── MergeCell.xlsx
├── OverflowNumericCell.xlsx
├── SharedStrings.xlsx
├── encryptAES.xlsx
├── encryptSHA1.xlsx
├── images
│ ├── background.jpg
│ ├── chart.png
│ ├── excel.bmp
│ ├── excel.emf
│ ├── excel.emz
│ ├── excel.gif
│ ├── excel.jpg
│ ├── excel.png
│ ├── excel.tif
│ ├── excel.wmf
│ └── excel.wmz
└── vbaProject.bin
├── vml.go
├── vmlDrawing.go
├── vml_test.go
├── workbook.go
├── workbook_test.go
├── xmlApp.go
├── xmlCalcChain.go
├── xmlChart.go
├── xmlChartSheet.go
├── xmlComments.go
├── xmlContentTypes.go
├── xmlCore.go
├── xmlDecodeDrawing.go
├── xmlDrawing.go
├── xmlMetaData.go
├── xmlPivotCache.go
├── xmlPivotTable.go
├── xmlSharedStrings.go
├── xmlSlicers.go
├── xmlStyles.go
├── xmlTable.go
├── xmlTheme.go
├── xmlWorkbook.go
└── xmlWorksheet.go
/.github/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.
6 |
7 | We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
8 |
9 | ## Our Standards
10 |
11 | Examples of behavior that contributes to a positive environment for our community include:
12 |
13 | * Demonstrating empathy and kindness toward other people
14 | * Being respectful of differing opinions, viewpoints, and experiences
15 | * Giving and gracefully accepting constructive feedback
16 | * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
17 | * Focusing on what is best not just for us as individuals, but for the overall community
18 |
19 | Examples of unacceptable behavior include:
20 |
21 | * The use of sexualized language or imagery, and sexual attention or advances of any kind
22 | * Trolling, insulting or derogatory comments, and personal or political attacks
23 | * Public or private harassment
24 | * Publishing others’ private information, such as a physical or email address, without their explicit permission
25 | * Other conduct which could reasonably be considered inappropriate in a professional setting
26 |
27 | ## Enforcement Responsibilities
28 |
29 | Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
30 |
31 | Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
32 |
33 | ## Scope
34 |
35 | This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
36 |
37 | ## Enforcement
38 |
39 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [xuri.me](https://xuri.me). All complaints will be reviewed and investigated promptly and fairly.
40 |
41 | All community leaders are obligated to respect the privacy and security of the reporter of any incident.
42 |
43 | ## Enforcement Guidelines
44 |
45 | Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
46 |
47 | ### 1. Correction
48 |
49 | Community Impact: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
50 |
51 | Consequence: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
52 |
53 | ### 2. Warning
54 |
55 | Community Impact: A violation through a single incident or series of actions.
56 |
57 | Consequence: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
58 |
59 | ### 3. Temporary Ban
60 |
61 | Community Impact: A serious violation of community standards, including sustained inappropriate behavior.
62 |
63 | Consequence: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
64 |
65 | ### 4. Permanent Ban
66 |
67 | Community Impact: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
68 |
69 | Consequence: A permanent ban from any sort of public interaction within the community.
70 |
71 | ## Attribution
72 |
73 | This Code of Conduct is adapted from the Contributor Covenant, version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html](https://www.contributor-covenant.org/version/2/1/code_of_conduct.html).
74 |
75 | Community Impact Guidelines were inspired by Mozilla’s code of conduct enforcement ladder.
76 |
77 | For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq). Translations are available at [https://www.contributor-covenant.org/translations](https://www.contributor-covenant.org/translations).
78 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: xuri
2 | open_collective: excelize
3 | patreon: xuri
4 | ko_fi: xurime
5 | liberapay: xuri
6 | issuehunt: xuri
7 | custom: https://www.paypal.com/paypalme/xuri
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.yml:
--------------------------------------------------------------------------------
1 | name: Bug report
2 | description: Create a report to help us improve
3 | body:
4 | - type: markdown
5 | attributes:
6 | value: |
7 | If you are reporting a new issue, make sure that we do not have any duplicates already open. You can ensure this by searching the issue list for this repository. If there is a duplicate, please close your issue and add a comment to the existing issue instead.
8 |
9 | - type: textarea
10 | id: description
11 | attributes:
12 | label: Description
13 | description: Briefly describe the problem you are having in a few paragraphs.
14 | validations:
15 | required: true
16 |
17 | - type: textarea
18 | id: reproduction-steps
19 | attributes:
20 | label: Steps to reproduce the issue
21 | description: Explain how to cause the issue in the provided reproduction.
22 | placeholder: |
23 | 1.
24 | 2.
25 | 3.
26 | validations:
27 | required: true
28 |
29 | - type: textarea
30 | id: received
31 | attributes:
32 | label: Describe the results you received
33 | validations:
34 | required: true
35 |
36 | - type: textarea
37 | id: expected
38 | attributes:
39 | label: Describe the results you expected
40 | validations:
41 | required: true
42 |
43 | - type: input
44 | id: go-version
45 | attributes:
46 | label: Go version
47 | description: |
48 | Output of `go version`:
49 | placeholder: e.g. 1.23.4
50 | validations:
51 | required: true
52 |
53 | - type: input
54 | id: excelize-version
55 | attributes:
56 | label: Excelize version or commit ID
57 | description: |
58 | Which version of Excelize are you using?
59 | placeholder: e.g. 2.9.0
60 | validations:
61 | required: true
62 |
63 | - type: textarea
64 | id: env
65 | attributes:
66 | label: Environment
67 | description: Environment details (OS, Microsoft Excel™ version, physical, etc.)
68 | render: shell
69 | validations:
70 | required: true
71 |
72 | - type: checkboxes
73 | id: checkboxes
74 | attributes:
75 | label: Validations
76 | description: Before submitting the issue, please make sure you do the following
77 | options:
78 | - label: Check that there isn't already an issue that reports the same bug to avoid creating a duplicate.
79 | required: true
80 | - label: The provided reproduction is a minimal reproducible example of the bug.
81 | required: true
82 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.yml:
--------------------------------------------------------------------------------
1 | name: Feature request
2 | description: Suggest an idea for this project
3 | body:
4 | - type: markdown
5 | attributes:
6 | value: |
7 | If you are reporting a new issue, make sure that we do not have any duplicates already open. You can ensure this by searching the issue list for this repository. If there is a duplicate, please close your issue and add a comment to the existing issue instead.
8 |
9 | - type: textarea
10 | id: description
11 | attributes:
12 | label: Description
13 | description: Describe the feature that you would like added
14 | validations:
15 | required: true
16 |
17 | - type: textarea
18 | id: additional-context
19 | attributes:
20 | label: Additional context
21 | description: Any other context or screenshots about the feature request here?
22 |
23 | - type: checkboxes
24 | id: checkboxes
25 | attributes:
26 | label: Validations
27 | description: Before submitting the issue, please make sure you do the following
28 | options:
29 | - label: Check that there isn't already an issue that requests the same feature to avoid creating a duplicate.
30 | required: true
31 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | # PR Details
2 |
3 |
4 |
5 | ## Description
6 |
7 |
8 |
9 | ## Related Issue
10 |
11 |
12 |
13 |
14 |
15 |
16 | ## Motivation and Context
17 |
18 |
19 |
20 | ## How Has This Been Tested
21 |
22 |
23 |
24 |
25 |
26 | ## Types of changes
27 |
28 |
29 |
30 | - [ ] Docs change / refactoring / dependency upgrade
31 | - [ ] Bug fix (non-breaking change which fixes an issue)
32 | - [ ] New feature (non-breaking change which adds functionality)
33 | - [ ] Breaking change (fix or feature that would cause existing functionality to change)
34 |
35 | ## Checklist
36 |
37 |
38 |
39 |
40 | - [ ] My code follows the code style of this project.
41 | - [ ] My change requires a change to the documentation.
42 | - [ ] I have updated the documentation accordingly.
43 | - [ ] I have read the **CONTRIBUTING** document.
44 | - [ ] I have added tests to cover my changes.
45 | - [ ] All new and existing tests passed.
46 |
--------------------------------------------------------------------------------
/.github/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Supported Versions
4 |
5 | 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.
6 |
7 | ## Reporting a Vulnerability
8 |
9 | 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!
10 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: github-actions
4 | directory: /
5 | schedule:
6 | interval: monthly
7 |
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | name: "CodeQL"
2 |
3 | on:
4 | push:
5 | branches: [master]
6 | pull_request:
7 | branches: [master]
8 | schedule:
9 | - cron: '0 6 * * 3'
10 |
11 | jobs:
12 | analyze:
13 | name: Analyze
14 | runs-on: ubuntu-24.04
15 |
16 | strategy:
17 | fail-fast: false
18 | matrix:
19 | language: ['go']
20 |
21 | steps:
22 | - name: Checkout repository
23 | uses: actions/checkout@v4
24 |
25 | # Initializes the CodeQL tools for scanning.
26 | - name: Initialize CodeQL
27 | uses: github/codeql-action/init@v3
28 | with:
29 | languages: ${{ matrix.language }}
30 |
31 | - name: Autobuild
32 | uses: github/codeql-action/autobuild@v3
33 |
34 | - name: Perform CodeQL Analysis
35 | uses: github/codeql-action/analyze@v3
36 |
--------------------------------------------------------------------------------
/.github/workflows/go.yml:
--------------------------------------------------------------------------------
1 | on: [push, pull_request]
2 | name: build
3 | jobs:
4 |
5 | test:
6 | strategy:
7 | matrix:
8 | go-version: [1.23.x, 1.24.x]
9 | os: [ubuntu-24.04, macos-13, windows-latest]
10 | targetplatform: [x86, x64]
11 |
12 | runs-on: ${{ matrix.os }}
13 |
14 | steps:
15 |
16 | - name: Install Go
17 | uses: actions/setup-go@v5
18 | with:
19 | go-version: ${{ matrix.go-version }}
20 | cache: false
21 |
22 | - name: Checkout code
23 | uses: actions/checkout@v4
24 |
25 | - name: Get dependencies
26 | run: |
27 | env GO111MODULE=on go vet ./...
28 | - name: Build
29 | run: go build -v .
30 |
31 | - name: Build on ARM
32 | if: runner.os == 'Linux'
33 | run: |
34 | GOARCH=arm GOARM=5 go build .
35 | GOARCH=arm GOARM=6 go build .
36 | GOARCH=arm GOARM=7 go build .
37 | GOARCH=arm64 go build .
38 | GOARCH=arm64 GOOS=android go build .
39 |
40 | - name: Test
41 | run: env GO111MODULE=on go test -v -timeout 50m -race ./... -coverprofile='coverage.txt' -covermode=atomic
42 |
43 | - name: Codecov
44 | uses: codecov/codecov-action@v5
45 | env:
46 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
47 | with:
48 | files: coverage.txt
49 | flags: unittests
50 | name: codecov-umbrella
51 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .idea
3 | *.json
4 | *.out
5 | *.test
6 | ~$*.xlsx
7 | test/*.png
8 | test/BadWorkbook.SaveAsEmptyStruct.xlsx
9 | test/Encryption*.xlsx
10 | test/excelize-*
11 | test/Test*.xlam
12 | test/Test*.xlsm
13 | test/Test*.xlsx
14 | test/Test*.xltm
15 | test/Test*.xltx
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2016-2025 The excelize Authors.
4 | Copyright (c) 2011-2017 Geoffrey J. Teale
5 | All rights reserved.
6 |
7 | Redistribution and use in source and binary forms, with or without
8 | modification, are permitted provided that the following conditions are met:
9 |
10 | * Redistributions of source code must retain the above copyright notice, this
11 | list of conditions and the following disclaimer.
12 |
13 | * Redistributions in binary form must reproduce the above copyright notice,
14 | this list of conditions and the following disclaimer in the documentation
15 | and/or other materials provided with the distribution.
16 |
17 | * Neither the name of the copyright holder nor the names of its
18 | contributors may be used to endorse or promote products derived from
19 | this software without specific prior written permission.
20 |
21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
25 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |

2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | # Excelize
13 |
14 | ## Introduction
15 |
16 | Excelize is a library written in pure Go providing a set of functions that allow you to write to and read from XLAM / XLSM / XLSX / XLTM / XLTX 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.23.0 or later. The full docs can be seen using go's built-in documentation tool, or online at [go.dev](https://pkg.go.dev/github.com/xuri/excelize/v2) and [docs reference](https://xuri.me/excelize/).
17 |
18 | ## Basic Usage
19 |
20 | ### Installation
21 |
22 | ```bash
23 | go get github.com/xuri/excelize
24 | ```
25 |
26 | - If your packages are managed using [Go Modules](https://go.dev/blog/using-go-modules), please install with following command.
27 |
28 | ```bash
29 | go get github.com/xuri/excelize/v2
30 | ```
31 |
32 | ### Create spreadsheet
33 |
34 | Here is a minimal example usage that will create spreadsheet file.
35 |
36 | ```go
37 | package main
38 |
39 | import (
40 | "fmt"
41 |
42 | "github.com/xuri/excelize/v2"
43 | )
44 |
45 | func main() {
46 | f := excelize.NewFile()
47 | defer func() {
48 | if err := f.Close(); err != nil {
49 | fmt.Println(err)
50 | }
51 | }()
52 | // Create a new sheet.
53 | index, err := f.NewSheet("Sheet2")
54 | if err != nil {
55 | fmt.Println(err)
56 | return
57 | }
58 | // Set value of a cell.
59 | f.SetCellValue("Sheet2", "A2", "Hello world.")
60 | f.SetCellValue("Sheet1", "B2", 100)
61 | // Set active sheet of the workbook.
62 | f.SetActiveSheet(index)
63 | // Save spreadsheet by the given path.
64 | if err := f.SaveAs("Book1.xlsx"); err != nil {
65 | fmt.Println(err)
66 | }
67 | }
68 | ```
69 |
70 | ### Reading spreadsheet
71 |
72 | The following constitutes the bare to read a spreadsheet document.
73 |
74 | ```go
75 | package main
76 |
77 | import (
78 | "fmt"
79 |
80 | "github.com/xuri/excelize/v2"
81 | )
82 |
83 | func main() {
84 | f, err := excelize.OpenFile("Book1.xlsx")
85 | if err != nil {
86 | fmt.Println(err)
87 | return
88 | }
89 | defer func() {
90 | // Close the spreadsheet.
91 | if err := f.Close(); err != nil {
92 | fmt.Println(err)
93 | }
94 | }()
95 | // Get value from cell by given worksheet name and cell reference.
96 | cell, err := f.GetCellValue("Sheet1", "B2")
97 | if err != nil {
98 | fmt.Println(err)
99 | return
100 | }
101 | fmt.Println(cell)
102 | // Get all the rows in the Sheet1.
103 | rows, err := f.GetRows("Sheet1")
104 | if err != nil {
105 | fmt.Println(err)
106 | return
107 | }
108 | for _, row := range rows {
109 | for _, colCell := range row {
110 | fmt.Print(colCell, "\t")
111 | }
112 | fmt.Println()
113 | }
114 | }
115 | ```
116 |
117 | ### Add chart to spreadsheet file
118 |
119 | With Excelize chart generation and management is as easy as a few lines of code. You can build charts based on data in your worksheet or generate charts without any data in your worksheet at all.
120 |
121 | 
122 |
123 | ```go
124 | package main
125 |
126 | import (
127 | "fmt"
128 |
129 | "github.com/xuri/excelize/v2"
130 | )
131 |
132 | func main() {
133 | f := excelize.NewFile()
134 | defer func() {
135 | if err := f.Close(); err != nil {
136 | fmt.Println(err)
137 | }
138 | }()
139 | for idx, row := range [][]interface{}{
140 | {nil, "Apple", "Orange", "Pear"}, {"Small", 2, 3, 3},
141 | {"Normal", 5, 2, 4}, {"Large", 6, 7, 8},
142 | } {
143 | cell, err := excelize.CoordinatesToCellName(1, idx+1)
144 | if err != nil {
145 | fmt.Println(err)
146 | return
147 | }
148 | f.SetSheetRow("Sheet1", cell, &row)
149 | }
150 | if err := f.AddChart("Sheet1", "E1", &excelize.Chart{
151 | Type: excelize.Col3DClustered,
152 | Series: []excelize.ChartSeries{
153 | {
154 | Name: "Sheet1!$A$2",
155 | Categories: "Sheet1!$B$1:$D$1",
156 | Values: "Sheet1!$B$2:$D$2",
157 | },
158 | {
159 | Name: "Sheet1!$A$3",
160 | Categories: "Sheet1!$B$1:$D$1",
161 | Values: "Sheet1!$B$3:$D$3",
162 | },
163 | {
164 | Name: "Sheet1!$A$4",
165 | Categories: "Sheet1!$B$1:$D$1",
166 | Values: "Sheet1!$B$4:$D$4",
167 | }},
168 | Title: []excelize.RichTextRun{
169 | {
170 | Text: "Fruit 3D Clustered Column Chart",
171 | },
172 | },
173 | }); err != nil {
174 | fmt.Println(err)
175 | return
176 | }
177 | // Save spreadsheet by the given path.
178 | if err := f.SaveAs("Book1.xlsx"); err != nil {
179 | fmt.Println(err)
180 | }
181 | }
182 | ```
183 |
184 | ### Add picture to spreadsheet file
185 |
186 | ```go
187 | package main
188 |
189 | import (
190 | "fmt"
191 | _ "image/gif"
192 | _ "image/jpeg"
193 | _ "image/png"
194 |
195 | "github.com/xuri/excelize/v2"
196 | )
197 |
198 | func main() {
199 | f, err := excelize.OpenFile("Book1.xlsx")
200 | if err != nil {
201 | fmt.Println(err)
202 | return
203 | }
204 | defer func() {
205 | // Close the spreadsheet.
206 | if err := f.Close(); err != nil {
207 | fmt.Println(err)
208 | }
209 | }()
210 | // Insert a picture.
211 | if err := f.AddPicture("Sheet1", "A2", "image.png", nil); err != nil {
212 | fmt.Println(err)
213 | }
214 | // Insert a picture to worksheet with scaling.
215 | if err := f.AddPicture("Sheet1", "D2", "image.jpg",
216 | &excelize.GraphicOptions{ScaleX: 0.5, ScaleY: 0.5}); err != nil {
217 | fmt.Println(err)
218 | }
219 | // Insert a picture offset in the cell with printing support.
220 | enable, disable := true, false
221 | if err := f.AddPicture("Sheet1", "H2", "image.gif",
222 | &excelize.GraphicOptions{
223 | PrintObject: &enable,
224 | LockAspectRatio: false,
225 | OffsetX: 15,
226 | OffsetY: 10,
227 | Locked: &disable,
228 | }); err != nil {
229 | fmt.Println(err)
230 | }
231 | // Save the spreadsheet with the origin path.
232 | if err = f.Save(); err != nil {
233 | fmt.Println(err)
234 | }
235 | }
236 | ```
237 |
238 | ## Contributing
239 |
240 | Contributions are welcome! Open a pull request to fix a bug, or open an issue to discuss a new feature or change. XML is compliant with [part 1 of the 5th edition of the ECMA-376 Standard for Office Open XML](https://www.ecma-international.org/publications-and-standards/standards/ecma-376/).
241 |
242 | ## Licenses
243 |
244 | This program is under the terms of the BSD 3-Clause License. See [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause).
245 |
246 | The Excel logo is a trademark of [Microsoft Corporation](https://aka.ms/trademarks-usage). This artwork is an adaptation.
247 |
248 | 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/).
249 |
--------------------------------------------------------------------------------
/README_zh.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | # Excelize
13 |
14 | ## 简介
15 |
16 | Excelize 是 Go 语言编写的用于操作 Office Excel 文档基础库,基于 ECMA-376,ISO/IEC 29500 国际标准。可以使用它来读取、写入由 Microsoft Excel™ 2007 及以上版本创建的电子表格文档。支持 XLAM / XLSM / XLSX / XLTM / XLTX 等多种文档格式,高度兼容带有样式、图片(表)、透视表、切片器等复杂组件的文档,并提供流式读写函数,用于处理包含大规模数据的工作簿。可应用于各类报表平台、云计算、边缘计算等系统。使用本类库要求使用的 Go 语言为 1.23.0 或更高版本。完整的使用文档请访问 [go.dev](https://pkg.go.dev/github.com/xuri/excelize/v2) 或查看 [参考文档](https://xuri.me/excelize/)。
17 |
18 | ## 快速上手
19 |
20 | ### 安装
21 |
22 | ```bash
23 | go get github.com/xuri/excelize
24 | ```
25 |
26 | - 如果您使用 [Go Modules](https://go.dev/blog/using-go-modules) 管理软件包,请使用下面的命令来安装最新版本。
27 |
28 | ```bash
29 | go get github.com/xuri/excelize/v2
30 | ```
31 |
32 | ### 创建 Excel 文档
33 |
34 | 下面是一个创建 Excel 文档的简单例子:
35 |
36 | ```go
37 | package main
38 |
39 | import (
40 | "fmt"
41 |
42 | "github.com/xuri/excelize/v2"
43 | )
44 |
45 | func main() {
46 | f := excelize.NewFile()
47 | defer func() {
48 | if err := f.Close(); err != nil {
49 | fmt.Println(err)
50 | }
51 | }()
52 | // 创建一个工作表
53 | index, err := f.NewSheet("Sheet2")
54 | if err != nil {
55 | fmt.Println(err)
56 | return
57 | }
58 | // 设置单元格的值
59 | f.SetCellValue("Sheet2", "A2", "Hello world.")
60 | f.SetCellValue("Sheet1", "B2", 100)
61 | // 设置工作簿的默认工作表
62 | f.SetActiveSheet(index)
63 | // 根据指定路径保存文件
64 | if err := f.SaveAs("Book1.xlsx"); err != nil {
65 | fmt.Println(err)
66 | }
67 | }
68 | ```
69 |
70 | ### 读取 Excel 文档
71 |
72 | 下面是读取 Excel 文档的例子:
73 |
74 | ```go
75 | package main
76 |
77 | import (
78 | "fmt"
79 |
80 | "github.com/xuri/excelize/v2"
81 | )
82 |
83 | func main() {
84 | f, err := excelize.OpenFile("Book1.xlsx")
85 | if err != nil {
86 | fmt.Println(err)
87 | return
88 | }
89 | defer func() {
90 | // 关闭工作簿
91 | if err := f.Close(); err != nil {
92 | fmt.Println(err)
93 | }
94 | }()
95 | // 获取工作表中指定单元格的值
96 | cell, err := f.GetCellValue("Sheet1", "B2")
97 | if err != nil {
98 | fmt.Println(err)
99 | return
100 | }
101 | fmt.Println(cell)
102 | // 获取 Sheet1 上所有单元格
103 | rows, err := f.GetRows("Sheet1")
104 | if err != nil {
105 | fmt.Println(err)
106 | return
107 | }
108 | for _, row := range rows {
109 | for _, colCell := range row {
110 | fmt.Print(colCell, "\t")
111 | }
112 | fmt.Println()
113 | }
114 | }
115 | ```
116 |
117 | ### 在 Excel 文档中创建图表
118 |
119 | 使用 Excelize 生成图表十分简单,仅需几行代码。您可以根据工作表中的已有数据构建图表,或向工作表中添加数据并创建图表。
120 |
121 | 
122 |
123 | ```go
124 | package main
125 |
126 | import (
127 | "fmt"
128 |
129 | "github.com/xuri/excelize/v2"
130 | )
131 |
132 | func main() {
133 | f := excelize.NewFile()
134 | defer func() {
135 | if err := f.Close(); err != nil {
136 | fmt.Println(err)
137 | }
138 | }()
139 | for idx, row := range [][]interface{}{
140 | {nil, "Apple", "Orange", "Pear"}, {"Small", 2, 3, 3},
141 | {"Normal", 5, 2, 4}, {"Large", 6, 7, 8},
142 | } {
143 | cell, err := excelize.CoordinatesToCellName(1, idx+1)
144 | if err != nil {
145 | fmt.Println(err)
146 | return
147 | }
148 | f.SetSheetRow("Sheet1", cell, &row)
149 | }
150 | if err := f.AddChart("Sheet1", "E1", &excelize.Chart{
151 | Type: excelize.Col3DClustered,
152 | Series: []excelize.ChartSeries{
153 | {
154 | Name: "Sheet1!$A$2",
155 | Categories: "Sheet1!$B$1:$D$1",
156 | Values: "Sheet1!$B$2:$D$2",
157 | },
158 | {
159 | Name: "Sheet1!$A$3",
160 | Categories: "Sheet1!$B$1:$D$1",
161 | Values: "Sheet1!$B$3:$D$3",
162 | },
163 | {
164 | Name: "Sheet1!$A$4",
165 | Categories: "Sheet1!$B$1:$D$1",
166 | Values: "Sheet1!$B$4:$D$4",
167 | }},
168 | Title: []excelize.RichTextRun{
169 | {
170 | Text: "Fruit 3D Clustered Column Chart",
171 | },
172 | },
173 | }); err != nil {
174 | fmt.Println(err)
175 | return
176 | }
177 | // 根据指定路径保存文件
178 | if err := f.SaveAs("Book1.xlsx"); err != nil {
179 | fmt.Println(err)
180 | }
181 | }
182 | ```
183 |
184 | ### 向 Excel 文档中插入图片
185 |
186 | ```go
187 | package main
188 |
189 | import (
190 | "fmt"
191 | _ "image/gif"
192 | _ "image/jpeg"
193 | _ "image/png"
194 |
195 | "github.com/xuri/excelize/v2"
196 | )
197 |
198 | func main() {
199 | f, err := excelize.OpenFile("Book1.xlsx")
200 | if err != nil {
201 | fmt.Println(err)
202 | return
203 | }
204 | defer func() {
205 | // 关闭工作簿
206 | if err := f.Close(); err != nil {
207 | fmt.Println(err)
208 | }
209 | }()
210 | // 插入图片
211 | if err := f.AddPicture("Sheet1", "A2", "image.png", nil); err != nil {
212 | fmt.Println(err)
213 | }
214 | // 在工作表中插入图片,并设置图片的缩放比例
215 | if err := f.AddPicture("Sheet1", "D2", "image.jpg",
216 | &excelize.GraphicOptions{ScaleX: 0.5, ScaleY: 0.5}); err != nil {
217 | fmt.Println(err)
218 | }
219 | // 在工作表中插入图片,并设置图片的打印属性
220 | enable, disable := true, false
221 | if err := f.AddPicture("Sheet1", "H2", "image.gif",
222 | &excelize.GraphicOptions{
223 | PrintObject: &enable,
224 | LockAspectRatio: false,
225 | OffsetX: 15,
226 | OffsetY: 10,
227 | Locked: &disable,
228 | }); err != nil {
229 | fmt.Println(err)
230 | }
231 | // 保存工作簿
232 | if err = f.Save(); err != nil {
233 | fmt.Println(err)
234 | }
235 | }
236 | ```
237 |
238 | ## 社区合作
239 |
240 | 欢迎您为此项目贡献代码,提出建议或问题、修复 Bug 以及参与讨论对新功能的想法。 XML 符合标准: [part 1 of the 5th edition of the ECMA-376 Standard for Office Open XML](https://www.ecma-international.org/publications-and-standards/standards/ecma-376/)。
241 |
242 | ## 开源许可
243 |
244 | 本项目遵循 BSD 3-Clause 开源许可协议,访问 [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) 查看许可协议文件。
245 |
246 | Excel 徽标是 [Microsoft Corporation](https://aka.ms/trademarks-usage) 的商标,项目的图片是一种改编。
247 |
248 | gopher.{ai,svg,png} 由 [Takuya Ueda](https://twitter.com/tenntenn) 创作,遵循 [Creative Commons 3.0 Attributions license](http://creativecommons.org/licenses/by/3.0/) 创作共用授权条款。
249 |
--------------------------------------------------------------------------------
/calcchain.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 - 2025 The excelize Authors. All rights reserved. Use of
2 | // this source code is governed by a BSD-style license that can be found in
3 | // the LICENSE file.
4 | //
5 | // Package excelize providing a set of functions that allow you to write to and
6 | // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
7 | // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
8 | // Supports complex components by high compatibility, and provided streaming
9 | // API for generating or reading data from a worksheet with huge amounts of
10 | // data. This library needs Go version 1.23 or later.
11 |
12 | package excelize
13 |
14 | import (
15 | "bytes"
16 | "encoding/xml"
17 | "io"
18 | )
19 |
20 | // calcChainReader provides a function to get the pointer to the structure
21 | // after deserialization of xl/calcChain.xml.
22 | func (f *File) calcChainReader() (*xlsxCalcChain, error) {
23 | if f.CalcChain == nil {
24 | f.CalcChain = new(xlsxCalcChain)
25 | if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathCalcChain)))).
26 | Decode(f.CalcChain); err != nil && err != io.EOF {
27 | return f.CalcChain, err
28 | }
29 | }
30 | return f.CalcChain, nil
31 | }
32 |
33 | // calcChainWriter provides a function to save xl/calcChain.xml after
34 | // serialize structure.
35 | func (f *File) calcChainWriter() {
36 | if f.CalcChain != nil && f.CalcChain.C != nil {
37 | output, _ := xml.Marshal(f.CalcChain)
38 | f.saveFileList(defaultXMLPathCalcChain, output)
39 | }
40 | }
41 |
42 | // deleteCalcChain provides a function to remove cell reference on the
43 | // calculation chain.
44 | func (f *File) deleteCalcChain(index int, cell string) error {
45 | calc, err := f.calcChainReader()
46 | if err != nil {
47 | return err
48 | }
49 | if calc != nil {
50 | calc.C = xlsxCalcChainCollection(calc.C).Filter(func(c xlsxCalcChainC) bool {
51 | return !((c.I == index && c.R == cell) || (c.I == index && cell == "") || (c.I == 0 && c.R == cell))
52 | })
53 | }
54 | if len(calc.C) == 0 {
55 | f.CalcChain = nil
56 | f.Pkg.Delete(defaultXMLPathCalcChain)
57 | content, err := f.contentTypesReader()
58 | if err != nil {
59 | return err
60 | }
61 | content.mu.Lock()
62 | defer content.mu.Unlock()
63 | for k, v := range content.Overrides {
64 | if v.PartName == "/xl/calcChain.xml" {
65 | content.Overrides = append(content.Overrides[:k], content.Overrides[k+1:]...)
66 | }
67 | }
68 | }
69 | return err
70 | }
71 |
72 | type xlsxCalcChainCollection []xlsxCalcChainC
73 |
74 | // Filter provides a function to filter calculation chain.
75 | func (c xlsxCalcChainCollection) Filter(fn func(v xlsxCalcChainC) bool) []xlsxCalcChainC {
76 | var results []xlsxCalcChainC
77 | for _, v := range c {
78 | if fn(v) {
79 | results = append(results, v)
80 | }
81 | }
82 | return results
83 | }
84 |
85 | // volatileDepsReader provides a function to get the pointer to the structure
86 | // after deserialization of xl/volatileDependencies.xml.
87 | func (f *File) volatileDepsReader() (*xlsxVolTypes, error) {
88 | if f.VolatileDeps == nil {
89 | volatileDeps, ok := f.Pkg.Load(defaultXMLPathVolatileDeps)
90 | if !ok {
91 | return f.VolatileDeps, nil
92 | }
93 | f.VolatileDeps = new(xlsxVolTypes)
94 | if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(volatileDeps.([]byte)))).
95 | Decode(f.VolatileDeps); err != nil && err != io.EOF {
96 | return f.VolatileDeps, err
97 | }
98 | }
99 | return f.VolatileDeps, nil
100 | }
101 |
102 | // volatileDepsWriter provides a function to save xl/volatileDependencies.xml
103 | // after serialize structure.
104 | func (f *File) volatileDepsWriter() {
105 | if f.VolatileDeps != nil {
106 | output, _ := xml.Marshal(f.VolatileDeps)
107 | f.saveFileList(defaultXMLPathVolatileDeps, output)
108 | }
109 | }
110 |
111 | // deleteVolTopicRef provides a function to remove cell reference on the
112 | // volatile dependencies topic.
113 | func (vt *xlsxVolTypes) deleteVolTopicRef(i1, i2, i3, i4 int) {
114 | for i := range vt.VolType[i1].Main[i2].Tp[i3].Tr {
115 | if i == i4 {
116 | vt.VolType[i1].Main[i2].Tp[i3].Tr = append(vt.VolType[i1].Main[i2].Tp[i3].Tr[:i], vt.VolType[i1].Main[i2].Tp[i3].Tr[i+1:]...)
117 | }
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/calcchain_test.go:
--------------------------------------------------------------------------------
1 | package excelize
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestCalcChainReader(t *testing.T) {
10 | f := NewFile()
11 | // Test read calculation chain with unsupported charset
12 | f.CalcChain = nil
13 | f.Pkg.Store(defaultXMLPathCalcChain, MacintoshCyrillicCharset)
14 | _, err := f.calcChainReader()
15 | assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
16 | }
17 |
18 | func TestDeleteCalcChain(t *testing.T) {
19 | f := NewFile()
20 | f.CalcChain = &xlsxCalcChain{C: []xlsxCalcChainC{}}
21 | f.ContentTypes.Overrides = append(f.ContentTypes.Overrides, xlsxOverride{
22 | PartName: "/xl/calcChain.xml",
23 | })
24 | assert.NoError(t, f.deleteCalcChain(1, "A1"))
25 |
26 | f.CalcChain = nil
27 | f.Pkg.Store(defaultXMLPathCalcChain, MacintoshCyrillicCharset)
28 | assert.EqualError(t, f.deleteCalcChain(1, "A1"), "XML syntax error on line 1: invalid UTF-8")
29 |
30 | f.CalcChain = nil
31 | f.Pkg.Store(defaultXMLPathCalcChain, MacintoshCyrillicCharset)
32 | assert.EqualError(t, f.SetCellFormula("Sheet1", "A1", ""), "XML syntax error on line 1: invalid UTF-8")
33 |
34 | formulaType, ref := STCellFormulaTypeShared, "C1:C5"
35 | assert.NoError(t, f.SetCellFormula("Sheet1", "C1", "A1+B1", FormulaOpts{Ref: &ref, Type: &formulaType}))
36 |
37 | // Test delete calculation chain with unsupported charset calculation chain
38 | f.CalcChain = nil
39 | f.Pkg.Store(defaultXMLPathCalcChain, MacintoshCyrillicCharset)
40 | assert.EqualError(t, f.SetCellValue("Sheet1", "C1", true), "XML syntax error on line 1: invalid UTF-8")
41 |
42 | // Test delete calculation chain with unsupported charset content types
43 | f.ContentTypes = nil
44 | f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
45 | assert.EqualError(t, f.deleteCalcChain(1, "A1"), "XML syntax error on line 1: invalid UTF-8")
46 | }
47 |
--------------------------------------------------------------------------------
/crypt_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 - 2025 The excelize Authors. All rights reserved. Use of
2 | // this source code is governed by a BSD-style license that can be found in
3 | // the LICENSE file.
4 | //
5 | // Package excelize providing a set of functions that allow you to write to and
6 | // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
7 | // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
8 | // Supports complex components by high compatibility, and provided streaming
9 | // API for generating or reading data from a worksheet with huge amounts of
10 | // data. This library needs Go version 1.23 or later.
11 |
12 | package excelize
13 |
14 | import (
15 | "bytes"
16 | "encoding/binary"
17 | "os"
18 | "path/filepath"
19 | "strings"
20 | "testing"
21 |
22 | "github.com/richardlehane/mscfb"
23 | "github.com/stretchr/testify/assert"
24 | )
25 |
26 | func TestEncrypt(t *testing.T) {
27 | // Test decrypt spreadsheet with incorrect password
28 | _, err := OpenFile(filepath.Join("test", "encryptSHA1.xlsx"), Options{Password: "passwd"})
29 | assert.EqualError(t, err, ErrWorkbookPassword.Error())
30 | // Test decrypt spreadsheet with password
31 | f, err := OpenFile(filepath.Join("test", "encryptSHA1.xlsx"), Options{Password: "password"})
32 | assert.NoError(t, err)
33 | cell, err := f.GetCellValue("Sheet1", "A1")
34 | assert.NoError(t, err)
35 | assert.Equal(t, "SECRET", cell)
36 | assert.NoError(t, f.Close())
37 | // Test decrypt spreadsheet with unsupported encrypt mechanism
38 | raw, err := os.ReadFile(filepath.Join("test", "encryptAES.xlsx"))
39 | assert.NoError(t, err)
40 | raw[2050] = 3
41 | _, err = Decrypt(raw, &Options{Password: "password"})
42 | assert.Equal(t, ErrUnsupportedEncryptMechanism, err)
43 |
44 | // Test encrypt spreadsheet with invalid password
45 | assert.EqualError(t, f.SaveAs(filepath.Join("test", "Encryption.xlsx"), Options{Password: strings.Repeat("*", MaxFieldLength+1)}), ErrPasswordLengthInvalid.Error())
46 | // Test encrypt spreadsheet with new password
47 | assert.NoError(t, f.SaveAs(filepath.Join("test", "Encryption.xlsx"), Options{Password: "passwd"}))
48 | assert.NoError(t, f.Close())
49 | f, err = OpenFile(filepath.Join("test", "Encryption.xlsx"), Options{Password: "passwd"})
50 | assert.NoError(t, err)
51 | cell, err = f.GetCellValue("Sheet1", "A1")
52 | assert.NoError(t, err)
53 | assert.Equal(t, "SECRET", cell)
54 | // Test remove password by save workbook with options
55 | assert.NoError(t, f.Save(Options{Password: ""}))
56 | assert.NoError(t, f.Close())
57 |
58 | doc, err := mscfb.New(bytes.NewReader(raw))
59 | assert.NoError(t, err)
60 | encryptionInfoBuf, encryptedPackageBuf := extractPart(doc)
61 | binary.LittleEndian.PutUint64(encryptionInfoBuf[20:32], uint64(0))
62 | _, err = standardDecrypt(encryptionInfoBuf, encryptedPackageBuf, &Options{Password: "password"})
63 | assert.NoError(t, err)
64 | _, err = decrypt(nil, nil, nil)
65 | assert.EqualError(t, err, "crypto/aes: invalid key size 0")
66 | _, err = agileDecrypt(encryptionInfoBuf, MacintoshCyrillicCharset, &Options{Password: "password"})
67 | assert.EqualError(t, err, "XML syntax error on line 1: invalid character entity &0 (no semicolon)")
68 | _, err = convertPasswdToKey("password", nil, Encryption{
69 | KeyEncryptors: KeyEncryptors{KeyEncryptor: []KeyEncryptor{
70 | {EncryptedKey: EncryptedKey{KeyData: KeyData{SaltValue: "=="}}},
71 | }},
72 | })
73 | assert.EqualError(t, err, "illegal base64 data at input byte 0")
74 | _, err = createIV([]byte{0}, Encryption{KeyData: KeyData{SaltValue: "=="}})
75 | assert.EqualError(t, err, "illegal base64 data at input byte 0")
76 | }
77 |
78 | func TestEncryptionMechanism(t *testing.T) {
79 | mechanism, err := encryptionMechanism([]byte{3, 0, 3, 0})
80 | assert.Equal(t, mechanism, "extensible")
81 | assert.EqualError(t, err, ErrUnsupportedEncryptMechanism.Error())
82 | _, err = encryptionMechanism([]byte{})
83 | assert.EqualError(t, err, ErrUnknownEncryptMechanism.Error())
84 | }
85 |
86 | func TestHashing(t *testing.T) {
87 | assert.Equal(t, hashing("unsupportedHashAlgorithm", []byte{}), []byte(nil))
88 | }
89 |
90 | func TestGenISOPasswdHash(t *testing.T) {
91 | for hashAlgorithm, expected := range map[string][]string{
92 | "MD4": {"2lZQZUubVHLm/t6KsuHX4w==", "TTHjJdU70B/6Zq83XGhHVA=="},
93 | "MD5": {"HWbqyd4dKKCjk1fEhk2kuQ==", "8ADyorkumWCayIukRhlVKQ=="},
94 | "SHA-1": {"XErQIV3Ol+nhXkyCxrLTEQm+mSc=", "I3nDtyf59ASaNX1l6KpFnA=="},
95 | "SHA-256": {"7oqMFyfED+mPrzRIBQ+KpKT4SClMHEPOZldliP15xAA=", "ru1R/w3P3Jna2Qo+EE8QiA=="},
96 | "SHA-384": {"nMODLlxsC8vr0btcq0kp/jksg5FaI3az5Sjo1yZk+/x4bFzsuIvpDKUhJGAk/fzo", "Zjq9/jHlgOY6MzFDSlVNZg=="},
97 | "SHA-512": {"YZ6jrGOFQgVKK3rDK/0SHGGgxEmFJglQIIRamZc2PkxVtUBp54fQn96+jVXEOqo6dtCSanqksXGcm/h3KaiR4Q==", "p5s/bybHBPtusI7EydTIrg=="},
98 | } {
99 | hashValue, saltValue, err := genISOPasswdHash("password", hashAlgorithm, expected[1], int(sheetProtectionSpinCount))
100 | assert.NoError(t, err)
101 | assert.Equal(t, expected[0], hashValue)
102 | assert.Equal(t, expected[1], saltValue)
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/date.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 - 2025 The excelize Authors. All rights reserved. Use of
2 | // this source code is governed by a BSD-style license that can be found in
3 | // the LICENSE file.
4 | //
5 | // Package excelize providing a set of functions that allow you to write to and
6 | // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
7 | // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
8 | // Supports complex components by high compatibility, and provided streaming
9 | // API for generating or reading data from a worksheet with huge amounts of
10 | // data. This library needs Go version 1.23 or later.
11 |
12 | package excelize
13 |
14 | import (
15 | "math"
16 | "time"
17 | )
18 |
19 | const (
20 | nanosInADay = float64((24 * time.Hour) / time.Nanosecond)
21 | dayNanoseconds = 24 * time.Hour
22 | maxDuration = 290 * 364 * dayNanoseconds
23 | roundEpsilon = 1e-9
24 | )
25 |
26 | var (
27 | daysInMonth = []int{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
28 | excel1900Epoc = time.Date(1899, time.December, 30, 0, 0, 0, 0, time.UTC)
29 | excel1904Epoc = time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC)
30 | excelMinTime1900 = time.Date(1899, time.December, 31, 0, 0, 0, 0, time.UTC)
31 | excelBuggyPeriodStart = time.Date(1900, time.March, 1, 0, 0, 0, 0, time.UTC).Add(-time.Nanosecond)
32 | )
33 |
34 | // timeToExcelTime provides a function to convert time to Excel time.
35 | func timeToExcelTime(t time.Time, date1904 bool) (float64, error) {
36 | date := excelMinTime1900
37 | if date1904 {
38 | date = excel1904Epoc
39 | }
40 | if t.Before(date) {
41 | return 0, nil
42 | }
43 | tt, diff, result := t, t.Sub(date), 0.0
44 | for diff >= maxDuration {
45 | result += float64(maxDuration / dayNanoseconds)
46 | tt = tt.Add(-maxDuration)
47 | diff = tt.Sub(date)
48 | }
49 |
50 | rem := diff % dayNanoseconds
51 | result += float64(diff-rem)/float64(dayNanoseconds) + float64(rem)/float64(dayNanoseconds)
52 |
53 | // Excel dates after 28th February 1900 are actually one day out.
54 | // Excel behaves as though the date 29th February 1900 existed, which it didn't.
55 | // Microsoft intentionally included this bug in Excel so that it would remain compatible with the spreadsheet
56 | // program that had the majority market share at the time; Lotus 1-2-3.
57 | // https://www.myonlinetraininghub.com/excel-date-and-time
58 | if !date1904 && t.After(excelBuggyPeriodStart) {
59 | result++
60 | }
61 | return result, nil
62 | }
63 |
64 | // shiftJulianToNoon provides a function to process julian date to noon.
65 | func shiftJulianToNoon(julianDays, julianFraction float64) (float64, float64) {
66 | switch {
67 | case -0.5 < julianFraction && julianFraction < 0.5:
68 | julianFraction += 0.5
69 | case julianFraction >= 0.5:
70 | julianDays++
71 | julianFraction -= 0.5
72 | case julianFraction <= -0.5:
73 | julianDays--
74 | julianFraction += 1.5
75 | }
76 | return julianDays, julianFraction
77 | }
78 |
79 | // fractionOfADay provides a function to return the integer values for hour,
80 | // minutes, seconds and nanoseconds that comprised a given fraction of a day.
81 | // values would round to 1 us.
82 | func fractionOfADay(fraction float64) (hours, minutes, seconds, nanoseconds int) {
83 | const (
84 | c1us = 1e3
85 | c1s = 1e9
86 | c1day = 24 * 60 * 60 * c1s
87 | )
88 |
89 | frac := int64(c1day*fraction + c1us/2)
90 | nanoseconds = int((frac%c1s)/c1us) * c1us
91 | frac /= c1s
92 | seconds = int(frac % 60)
93 | frac /= 60
94 | minutes = int(frac % 60)
95 | hours = int(frac / 60)
96 | return
97 | }
98 |
99 | // julianDateToGregorianTime provides a function to convert julian date to
100 | // gregorian time.
101 | func julianDateToGregorianTime(part1, part2 float64) time.Time {
102 | part1I, part1F := math.Modf(part1)
103 | part2I, part2F := math.Modf(part2)
104 | julianDays := part1I + part2I
105 | julianFraction := part1F + part2F
106 | julianDays, julianFraction = shiftJulianToNoon(julianDays, julianFraction)
107 | day, month, year := doTheFliegelAndVanFlandernAlgorithm(int(julianDays))
108 | hours, minutes, seconds, nanoseconds := fractionOfADay(julianFraction)
109 | return time.Date(year, time.Month(month), day, hours, minutes, seconds, nanoseconds, time.UTC)
110 | }
111 |
112 | // doTheFliegelAndVanFlandernAlgorithm; By this point generations of
113 | // programmers have repeated the algorithm sent to the editor of
114 | // "Communications of the ACM" in 1968 (published in CACM, volume 11, number
115 | // 10, October 1968, p.657). None of those programmers seems to have found it
116 | // necessary to explain the constants or variable names set out by Henry F.
117 | // Fliegel and Thomas C. Van Flandern. Maybe one day I'll buy that journal and
118 | // expand an explanation here - that day is not today.
119 | func doTheFliegelAndVanFlandernAlgorithm(jd int) (day, month, year int) {
120 | l := jd + 68569
121 | n := (4 * l) / 146097
122 | l = l - (146097*n+3)/4
123 | i := (4000 * (l + 1)) / 1461001
124 | l = l - (1461*i)/4 + 31
125 | j := (80 * l) / 2447
126 | d := l - (2447*j)/80
127 | l = j / 11
128 | m := j + 2 - (12 * l)
129 | y := 100*(n-49) + i + l
130 | return d, m, y
131 | }
132 |
133 | // timeFromExcelTime provides a function to convert an excelTime
134 | // representation (stored as a floating point number) to a time.Time.
135 | func timeFromExcelTime(excelTime float64, date1904 bool) time.Time {
136 | var date time.Time
137 | wholeDaysPart := int(excelTime)
138 | // Excel uses Julian dates prior to March 1st 1900, and Gregorian
139 | // thereafter.
140 | if wholeDaysPart <= 61 {
141 | const OFFSET1900 = 15018.0
142 | const OFFSET1904 = 16480.0
143 | const MJD0 float64 = 2400000.5
144 | var date time.Time
145 | if date1904 {
146 | date = julianDateToGregorianTime(MJD0, excelTime+OFFSET1904)
147 | } else {
148 | date = julianDateToGregorianTime(MJD0, excelTime+OFFSET1900)
149 | }
150 | return date
151 | }
152 | floatPart := excelTime - float64(wholeDaysPart) + roundEpsilon
153 | if date1904 {
154 | date = excel1904Epoc
155 | } else {
156 | date = excel1900Epoc
157 | }
158 | durationPart := time.Duration(nanosInADay * floatPart)
159 | date = date.AddDate(0, 0, wholeDaysPart).Add(durationPart)
160 | if date.Nanosecond()/1e6 > 500 {
161 | return date.Round(time.Second)
162 | }
163 | return date.Truncate(time.Second)
164 | }
165 |
166 | // ExcelDateToTime converts a float-based Excel date representation to a time.Time.
167 | func ExcelDateToTime(excelDate float64, use1904Format bool) (time.Time, error) {
168 | if excelDate < 0 {
169 | return time.Time{}, newInvalidExcelDateError(excelDate)
170 | }
171 | return timeFromExcelTime(excelDate, use1904Format), nil
172 | }
173 |
174 | // isLeapYear determine if leap year for a given year.
175 | func isLeapYear(y int) bool {
176 | if y == y/400*400 {
177 | return true
178 | }
179 | if y == y/100*100 {
180 | return false
181 | }
182 | return y == y/4*4
183 | }
184 |
185 | // getDaysInMonth provides a function to get the days by a given year and
186 | // month number.
187 | func getDaysInMonth(y, m int) int {
188 | if m == 2 && isLeapYear(y) {
189 | return 29
190 | }
191 | return daysInMonth[m-1]
192 | }
193 |
194 | // validateDate provides a function to validate if a valid date by a given
195 | // year, month, and day number.
196 | func validateDate(y, m, d int) bool {
197 | if m < 1 || m > 12 {
198 | return false
199 | }
200 | if d < 1 {
201 | return false
202 | }
203 | return d <= getDaysInMonth(y, m)
204 | }
205 |
206 | // formatYear converts the given year number into a 4-digit format.
207 | func formatYear(y int) int {
208 | if y < 1900 {
209 | if y < 30 {
210 | y += 2000
211 | } else {
212 | y += 1900
213 | }
214 | }
215 | return y
216 | }
217 |
218 | // getDurationNumFmt returns most simplify numbers format code for time
219 | // duration type cell value by given worksheet name, cell reference and number.
220 | func getDurationNumFmt(d time.Duration) int {
221 | if d >= time.Hour*24 {
222 | return 46
223 | }
224 | // Whole minutes
225 | if d.Minutes() == float64(int(d.Minutes())) {
226 | return 20
227 | }
228 | return 21
229 | }
230 |
231 | // getTimeNumFmt returns most simplify numbers format code for time type cell
232 | // value by given worksheet name, cell reference and number.
233 | func getTimeNumFmt(t time.Time) int {
234 | nextMonth := t.AddDate(0, 1, 0)
235 | // Whole months
236 | if t.Day() == 1 && nextMonth.Day() == 1 {
237 | return 17
238 | }
239 | // Whole days
240 | if t.Hour() == 0 && t.Minute() == 0 && t.Second() == 0 && t.Nanosecond() == 0 {
241 | return 14
242 | }
243 | return 22
244 | }
245 |
--------------------------------------------------------------------------------
/date_test.go:
--------------------------------------------------------------------------------
1 | package excelize
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 | "time"
7 |
8 | "github.com/stretchr/testify/assert"
9 | )
10 |
11 | type dateTest struct {
12 | ExcelValue float64
13 | GoValue time.Time
14 | }
15 |
16 | var trueExpectedDateList = []dateTest{
17 | {0.0000000000000000, time.Date(1899, time.December, 30, 0, 0, 0, 0, time.UTC)},
18 | {25569.000000000000, time.Unix(0, 0).UTC()},
19 |
20 | // Expected values extracted from real spreadsheet
21 | {1.0000000000000000, time.Date(1900, time.January, 1, 0, 0, 0, 0, time.UTC)},
22 | {1.0000115740740740, time.Date(1900, time.January, 1, 0, 0, 1, 0, time.UTC)},
23 | {1.0006944444444446, time.Date(1900, time.January, 1, 0, 1, 0, 0, time.UTC)},
24 | {1.0416666666666667, time.Date(1900, time.January, 1, 1, 0, 0, 0, time.UTC)},
25 | {2.0000000000000000, time.Date(1900, time.January, 2, 0, 0, 0, 0, time.UTC)},
26 | {43269.000000000000, time.Date(2018, time.June, 18, 0, 0, 0, 0, time.UTC)},
27 | {43542.611111111109, time.Date(2019, time.March, 18, 14, 40, 0, 0, time.UTC)},
28 | {401769.00000000000, time.Date(3000, time.January, 1, 0, 0, 0, 0, time.UTC)},
29 | }
30 |
31 | var excelTimeInputList = []dateTest{
32 | {0.0, time.Date(1899, 12, 30, 0, 0, 0, 0, time.UTC)},
33 | {60.0, time.Date(1900, 2, 28, 0, 0, 0, 0, time.UTC)},
34 | {61.0, time.Date(1900, 3, 1, 0, 0, 0, 0, time.UTC)},
35 | {41275.0, time.Date(2013, 1, 1, 0, 0, 0, 0, time.UTC)},
36 | {44450.3333333333, time.Date(2021, time.September, 11, 8, 0, 0, 0, time.UTC)},
37 | {401769.0, time.Date(3000, 1, 1, 0, 0, 0, 0, time.UTC)},
38 | }
39 |
40 | func TestTimeToExcelTime(t *testing.T) {
41 | for i, test := range trueExpectedDateList {
42 | t.Run(fmt.Sprintf("TestData%d", i+1), func(t *testing.T) {
43 | excelTime, err := timeToExcelTime(test.GoValue, false)
44 | assert.NoError(t, err)
45 | assert.Equalf(t, test.ExcelValue, excelTime,
46 | "Time: %s", test.GoValue.String())
47 | })
48 | }
49 | }
50 |
51 | func TestTimeToExcelTime_Timezone(t *testing.T) {
52 | location, err := time.LoadLocation("America/Los_Angeles")
53 | if !assert.NoError(t, err) {
54 | t.FailNow()
55 | }
56 | for i, test := range trueExpectedDateList {
57 | t.Run(fmt.Sprintf("TestData%d", i+1), func(t *testing.T) {
58 | _, err := timeToExcelTime(test.GoValue.In(location), false)
59 | assert.NoError(t, err)
60 | })
61 | }
62 | }
63 |
64 | func TestTimeFromExcelTime(t *testing.T) {
65 | for i, test := range excelTimeInputList {
66 | t.Run(fmt.Sprintf("TestData%d", i+1), func(t *testing.T) {
67 | assert.Equal(t, test.GoValue, timeFromExcelTime(test.ExcelValue, false))
68 | })
69 | }
70 | for hour := 0; hour < 24; hour++ {
71 | for minVal := 0; minVal < 60; minVal++ {
72 | for sec := 0; sec < 60; sec++ {
73 | date := time.Date(2021, time.December, 30, hour, minVal, sec, 0, time.UTC)
74 | // Test use 1900 date system
75 | excel1900Time, err := timeToExcelTime(date, false)
76 | assert.NoError(t, err)
77 | date1900Out := timeFromExcelTime(excel1900Time, false)
78 | assert.EqualValues(t, hour, date1900Out.Hour())
79 | assert.EqualValues(t, minVal, date1900Out.Minute())
80 | assert.EqualValues(t, sec, date1900Out.Second())
81 | // Test use 1904 date system
82 | excel1904Time, err := timeToExcelTime(date, true)
83 | assert.NoError(t, err)
84 | date1904Out := timeFromExcelTime(excel1904Time, true)
85 | assert.EqualValues(t, hour, date1904Out.Hour())
86 | assert.EqualValues(t, minVal, date1904Out.Minute())
87 | assert.EqualValues(t, sec, date1904Out.Second())
88 | }
89 | }
90 | }
91 | }
92 |
93 | func TestTimeFromExcelTime_1904(t *testing.T) {
94 | julianDays, julianFraction := shiftJulianToNoon(1, -0.6)
95 | assert.Equal(t, julianDays, 0.0)
96 | assert.Equal(t, julianFraction, 0.9)
97 | julianDays, julianFraction = shiftJulianToNoon(1, 0.1)
98 | assert.Equal(t, julianDays, 1.0)
99 | assert.Equal(t, julianFraction, 0.6)
100 | assert.Equal(t, timeFromExcelTime(61, true), time.Date(1904, time.March, 2, 0, 0, 0, 0, time.UTC))
101 | assert.Equal(t, timeFromExcelTime(62, true), time.Date(1904, time.March, 3, 0, 0, 0, 0, time.UTC))
102 | }
103 |
104 | func TestExcelDateToTime(t *testing.T) {
105 | // Check normal case
106 | for i, test := range excelTimeInputList {
107 | t.Run(fmt.Sprintf("TestData%d", i+1), func(t *testing.T) {
108 | timeValue, err := ExcelDateToTime(test.ExcelValue, false)
109 | assert.Equal(t, test.GoValue, timeValue)
110 | assert.NoError(t, err)
111 | })
112 | }
113 | // Check error case
114 | _, err := ExcelDateToTime(-1, false)
115 | assert.EqualError(t, err, newInvalidExcelDateError(-1).Error())
116 | }
117 |
--------------------------------------------------------------------------------
/docProps.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 - 2025 The excelize Authors. All rights reserved. Use of
2 | // this source code is governed by a BSD-style license that can be found in
3 | // the LICENSE file.
4 | //
5 | // Package excelize providing a set of functions that allow you to write to and
6 | // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
7 | // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
8 | // Supports complex components by high compatibility, and provided streaming
9 | // API for generating or reading data from a worksheet with huge amounts of
10 | // data. This library needs Go version 1.23 or later.
11 |
12 | package excelize
13 |
14 | import (
15 | "bytes"
16 | "encoding/xml"
17 | "io"
18 | "reflect"
19 | )
20 |
21 | // SetAppProps provides a function to set document application properties. The
22 | // properties that can be set are:
23 | //
24 | // Property | Description
25 | // -------------------+--------------------------------------------------------------------------
26 | // Application | The name of the application that created this document.
27 | // |
28 | // ScaleCrop | Indicates the display mode of the document thumbnail. Set this element
29 | // | to 'true' to enable scaling of the document thumbnail to the display. Set
30 | // | this element to 'false' to enable cropping of the document thumbnail to
31 | // | show only sections that will fit the display.
32 | // |
33 | // DocSecurity | Security level of a document as a numeric value. Document security is
34 | // | defined as:
35 | // | 1 - Document is password protected.
36 | // | 2 - Document is recommended to be opened as read-only.
37 | // | 3 - Document is enforced to be opened as read-only.
38 | // | 4 - Document is locked for annotation.
39 | // |
40 | // Company | The name of a company associated with the document.
41 | // |
42 | // LinksUpToDate | Indicates whether hyperlinks in a document are up-to-date. Set this
43 | // | element to 'true' to indicate that hyperlinks are updated. Set this
44 | // | element to 'false' to indicate that hyperlinks are outdated.
45 | // |
46 | // HyperlinksChanged | Specifies that one or more hyperlinks in this part were updated
47 | // | exclusively in this part by a producer. The next producer to open this
48 | // | document shall update the hyperlink relationships with the new
49 | // | hyperlinks specified in this part.
50 | // |
51 | // AppVersion | Specifies the version of the application which produced this document.
52 | // | The content of this element shall be of the form XX.YYYY where X and Y
53 | // | represent numerical values, or the document shall be considered
54 | // | non-conformant.
55 | //
56 | // For example:
57 | //
58 | // err := f.SetAppProps(&excelize.AppProperties{
59 | // Application: "Microsoft Excel",
60 | // ScaleCrop: true,
61 | // DocSecurity: 3,
62 | // Company: "Company Name",
63 | // LinksUpToDate: true,
64 | // HyperlinksChanged: true,
65 | // AppVersion: "16.0000",
66 | // })
67 | func (f *File) SetAppProps(appProperties *AppProperties) error {
68 | app := new(xlsxProperties)
69 | if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathDocPropsApp)))).
70 | Decode(app); err != nil && err != io.EOF {
71 | return err
72 | }
73 | setNoPtrFieldsVal([]string{"Application", "ScaleCrop", "DocSecurity", "Company", "LinksUpToDate", "HyperlinksChanged", "AppVersion"},
74 | reflect.ValueOf(*appProperties), reflect.ValueOf(app).Elem())
75 | app.Vt = NameSpaceDocumentPropertiesVariantTypes.Value
76 | output, err := xml.Marshal(app)
77 | f.saveFileList(defaultXMLPathDocPropsApp, output)
78 | return err
79 | }
80 |
81 | // GetAppProps provides a function to get document application properties.
82 | func (f *File) GetAppProps() (ret *AppProperties, err error) {
83 | app := new(xlsxProperties)
84 | if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathDocPropsApp)))).
85 | Decode(app); err != nil && err != io.EOF {
86 | return
87 | }
88 | ret, err = &AppProperties{
89 | Application: app.Application,
90 | ScaleCrop: app.ScaleCrop,
91 | DocSecurity: app.DocSecurity,
92 | Company: app.Company,
93 | LinksUpToDate: app.LinksUpToDate,
94 | HyperlinksChanged: app.HyperlinksChanged,
95 | AppVersion: app.AppVersion,
96 | }, nil
97 | return
98 | }
99 |
100 | // SetDocProps provides a function to set document core properties. The
101 | // properties that can be set are:
102 | //
103 | // Property | Description
104 | // ----------------+-----------------------------------------------------------
105 | // Title | The name given to the resource.
106 | // |
107 | // Subject | The topic of the content of the resource.
108 | // |
109 | // Creator | An entity primarily responsible for making the content of
110 | // | the resource.
111 | // |
112 | // Keywords | A delimited set of keywords to support searching and
113 | // | indexing. This is typically a list of terms that are not
114 | // | available elsewhere in the properties.
115 | // |
116 | // Description | An explanation of the content of the resource.
117 | // |
118 | // LastModifiedBy | The user who performed the last modification. The
119 | // | identification is environment-specific.
120 | // |
121 | // Language | The language of the intellectual content of the resource.
122 | // |
123 | // Identifier | An unambiguous reference to the resource within a given
124 | // | context.
125 | // |
126 | // Revision | The topic of the content of the resource.
127 | // |
128 | // ContentStatus | The status of the content. For example: Values might
129 | // | include "Draft", "Reviewed" and "Final"
130 | // |
131 | // Category | A categorization of the content of this package.
132 | // |
133 | // Version | The version number. This value is set by the user or by
134 | // | the application.
135 | // |
136 | // Created | The created time of the content of the resource which
137 | // | represent in ISO 8601 UTC format, for example
138 | // | "2019-06-04T22:00:10Z".
139 | // |
140 | // Modified | The modified time of the content of the resource which
141 | // | represent in ISO 8601 UTC format, for example
142 | // | "2019-06-04T22:00:10Z".
143 | // |
144 | //
145 | // For example:
146 | //
147 | // err := f.SetDocProps(&excelize.DocProperties{
148 | // Category: "category",
149 | // ContentStatus: "Draft",
150 | // Created: "2019-06-04T22:00:10Z",
151 | // Creator: "Go Excelize",
152 | // Description: "This file created by Go Excelize",
153 | // Identifier: "xlsx",
154 | // Keywords: "Spreadsheet",
155 | // LastModifiedBy: "Go Author",
156 | // Modified: "2019-06-04T22:00:10Z",
157 | // Revision: "0",
158 | // Subject: "Test Subject",
159 | // Title: "Test Title",
160 | // Language: "en-US",
161 | // Version: "1.0.0",
162 | // })
163 | func (f *File) SetDocProps(docProperties *DocProperties) error {
164 | core := new(decodeCoreProperties)
165 | if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathDocPropsCore)))).
166 | Decode(core); err != nil && err != io.EOF {
167 | return err
168 | }
169 | newProps := &xlsxCoreProperties{
170 | Dc: NameSpaceDublinCore,
171 | Dcterms: NameSpaceDublinCoreTerms,
172 | Dcmitype: NameSpaceDublinCoreMetadataInitiative,
173 | XSI: NameSpaceXMLSchemaInstance,
174 | Title: core.Title,
175 | Subject: core.Subject,
176 | Creator: core.Creator,
177 | Keywords: core.Keywords,
178 | Description: core.Description,
179 | LastModifiedBy: core.LastModifiedBy,
180 | Language: core.Language,
181 | Identifier: core.Identifier,
182 | Revision: core.Revision,
183 | ContentStatus: core.ContentStatus,
184 | Category: core.Category,
185 | Version: core.Version,
186 | }
187 | if core.Created != nil {
188 | newProps.Created = &xlsxDcTerms{Type: core.Created.Type, Text: core.Created.Text}
189 | }
190 | if core.Modified != nil {
191 | newProps.Modified = &xlsxDcTerms{Type: core.Modified.Type, Text: core.Modified.Text}
192 | }
193 | setNoPtrFieldsVal([]string{
194 | "Category", "ContentStatus", "Creator", "Description", "Identifier", "Keywords",
195 | "LastModifiedBy", "Revision", "Subject", "Title", "Language", "Version",
196 | }, reflect.ValueOf(*docProperties), reflect.ValueOf(newProps).Elem())
197 | if docProperties.Created != "" {
198 | newProps.Created = &xlsxDcTerms{Type: "dcterms:W3CDTF", Text: docProperties.Created}
199 | }
200 | if docProperties.Modified != "" {
201 | newProps.Modified = &xlsxDcTerms{Type: "dcterms:W3CDTF", Text: docProperties.Modified}
202 | }
203 | output, err := xml.Marshal(newProps)
204 | f.saveFileList(defaultXMLPathDocPropsCore, output)
205 |
206 | return err
207 | }
208 |
209 | // GetDocProps provides a function to get document core properties.
210 | func (f *File) GetDocProps() (ret *DocProperties, err error) {
211 | core := new(decodeCoreProperties)
212 |
213 | if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathDocPropsCore)))).
214 | Decode(core); err != nil && err != io.EOF {
215 | return
216 | }
217 | ret, err = &DocProperties{
218 | Category: core.Category,
219 | ContentStatus: core.ContentStatus,
220 | Creator: core.Creator,
221 | Description: core.Description,
222 | Identifier: core.Identifier,
223 | Keywords: core.Keywords,
224 | LastModifiedBy: core.LastModifiedBy,
225 | Revision: core.Revision,
226 | Subject: core.Subject,
227 | Title: core.Title,
228 | Language: core.Language,
229 | Version: core.Version,
230 | }, nil
231 | if core.Created != nil {
232 | ret.Created = core.Created.Text
233 | }
234 | if core.Modified != nil {
235 | ret.Modified = core.Modified.Text
236 | }
237 | return
238 | }
239 |
--------------------------------------------------------------------------------
/docProps_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 - 2025 The excelize Authors. All rights reserved. Use of
2 | // this source code is governed by a BSD-style license that can be found in
3 | // the LICENSE file.
4 | //
5 | // Package excelize providing a set of functions that allow you to write to and
6 | // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
7 | // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
8 | // Supports complex components by high compatibility, and provided streaming
9 | // API for generating or reading data from a worksheet with huge amounts of
10 | // data. This library needs Go version 1.23 or later.
11 |
12 | package excelize
13 |
14 | import (
15 | "path/filepath"
16 | "testing"
17 |
18 | "github.com/stretchr/testify/assert"
19 | )
20 |
21 | var MacintoshCyrillicCharset = []byte{0x8F, 0xF0, 0xE8, 0xE2, 0xE5, 0xF2, 0x20, 0xEC, 0xE8, 0xF0}
22 |
23 | func TestSetAppProps(t *testing.T) {
24 | f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
25 | if !assert.NoError(t, err) {
26 | t.FailNow()
27 | }
28 | assert.NoError(t, f.SetAppProps(&AppProperties{
29 | Application: "Microsoft Excel",
30 | ScaleCrop: true,
31 | DocSecurity: 3,
32 | Company: "Company Name",
33 | LinksUpToDate: true,
34 | HyperlinksChanged: true,
35 | AppVersion: "16.0000",
36 | }))
37 | assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetAppProps.xlsx")))
38 | f.Pkg.Store(defaultXMLPathDocPropsApp, nil)
39 | assert.NoError(t, f.SetAppProps(&AppProperties{}))
40 | assert.NoError(t, f.Close())
41 |
42 | // Test unsupported charset
43 | f = NewFile()
44 | f.Pkg.Store(defaultXMLPathDocPropsApp, MacintoshCyrillicCharset)
45 | assert.EqualError(t, f.SetAppProps(&AppProperties{}), "XML syntax error on line 1: invalid UTF-8")
46 | }
47 |
48 | func TestGetAppProps(t *testing.T) {
49 | f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
50 | if !assert.NoError(t, err) {
51 | t.FailNow()
52 | }
53 | props, err := f.GetAppProps()
54 | assert.NoError(t, err)
55 | assert.Equal(t, props.Application, "Microsoft Macintosh Excel")
56 | f.Pkg.Store(defaultXMLPathDocPropsApp, nil)
57 | _, err = f.GetAppProps()
58 | assert.NoError(t, err)
59 | assert.NoError(t, f.Close())
60 |
61 | // Test get application properties with unsupported charset
62 | f = NewFile()
63 | f.Pkg.Store(defaultXMLPathDocPropsApp, MacintoshCyrillicCharset)
64 | _, err = f.GetAppProps()
65 | assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
66 | }
67 |
68 | func TestSetDocProps(t *testing.T) {
69 | f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
70 | if !assert.NoError(t, err) {
71 | t.FailNow()
72 | }
73 | assert.NoError(t, f.SetDocProps(&DocProperties{
74 | Category: "category",
75 | ContentStatus: "Draft",
76 | Created: "2019-06-04T22:00:10Z",
77 | Creator: "Go Excelize",
78 | Description: "This file created by Go Excelize",
79 | Identifier: "xlsx",
80 | Keywords: "Spreadsheet",
81 | LastModifiedBy: "Go Author",
82 | Modified: "2019-06-04T22:00:10Z",
83 | Revision: "0",
84 | Subject: "Test Subject",
85 | Title: "Test Title",
86 | Language: "en-US",
87 | Version: "1.0.0",
88 | }))
89 | assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetDocProps.xlsx")))
90 | f.Pkg.Store(defaultXMLPathDocPropsCore, nil)
91 | assert.NoError(t, f.SetDocProps(&DocProperties{}))
92 | assert.NoError(t, f.Close())
93 |
94 | // Test unsupported charset
95 | f = NewFile()
96 | f.Pkg.Store(defaultXMLPathDocPropsCore, MacintoshCyrillicCharset)
97 | assert.EqualError(t, f.SetDocProps(&DocProperties{}), "XML syntax error on line 1: invalid UTF-8")
98 | }
99 |
100 | func TestGetDocProps(t *testing.T) {
101 | f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
102 | if !assert.NoError(t, err) {
103 | t.FailNow()
104 | }
105 | props, err := f.GetDocProps()
106 | assert.NoError(t, err)
107 | assert.Equal(t, props.Creator, "Microsoft Office User")
108 | f.Pkg.Store(defaultXMLPathDocPropsCore, nil)
109 | _, err = f.GetDocProps()
110 | assert.NoError(t, err)
111 | assert.NoError(t, f.Close())
112 |
113 | // Test get workbook properties with unsupported charset
114 | f = NewFile()
115 | f.Pkg.Store(defaultXMLPathDocPropsCore, MacintoshCyrillicCharset)
116 | _, err = f.GetDocProps()
117 | assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
118 | }
119 |
--------------------------------------------------------------------------------
/drawing_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 - 2025 The excelize Authors. All rights reserved. Use of
2 | // this source code is governed by a BSD-style license that can be found in
3 | // the LICENSE file.
4 | //
5 | // Package excelize providing a set of functions that allow you to write to and
6 | // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
7 | // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
8 | // Supports complex components by high compatibility, and provided streaming
9 | // API for generating or reading data from a worksheet with huge amounts of
10 | // data. This library needs Go version 1.23 or later.
11 |
12 | package excelize
13 |
14 | import (
15 | "encoding/xml"
16 | "sync"
17 | "testing"
18 |
19 | "github.com/stretchr/testify/assert"
20 | )
21 |
22 | func TestDrawingParser(t *testing.T) {
23 | f := File{
24 | Drawings: sync.Map{},
25 | Pkg: sync.Map{},
26 | }
27 | f.Pkg.Store("charset", MacintoshCyrillicCharset)
28 | f.Pkg.Store("wsDr", []byte(xml.Header+``))
29 | // Test with one cell anchor
30 | _, _, err := f.drawingParser("wsDr")
31 | assert.NoError(t, err)
32 | // Test with unsupported charset
33 | _, _, err = f.drawingParser("charset")
34 | assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
35 | // Test with alternate content
36 | f.Drawings = sync.Map{}
37 | f.Pkg.Store("wsDr", []byte(xml.Header+``))
38 | _, _, err = f.drawingParser("wsDr")
39 | assert.NoError(t, err)
40 | }
41 |
42 | func TestDeleteDrawingRels(t *testing.T) {
43 | f := NewFile()
44 | // Test delete drawing relationships with unsupported charset
45 | rels := "xl/drawings/_rels/drawing1.xml.rels"
46 | f.Relationships.Delete(rels)
47 | f.Pkg.Store(rels, MacintoshCyrillicCharset)
48 | f.deleteDrawingRels(rels, "")
49 | }
50 |
--------------------------------------------------------------------------------
/errors_test.go:
--------------------------------------------------------------------------------
1 | package excelize
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestNewInvalidColNameError(t *testing.T) {
10 | assert.EqualError(t, newInvalidColumnNameError("A"), "invalid column name \"A\"")
11 | assert.EqualError(t, newInvalidColumnNameError(""), "invalid column name \"\"")
12 | }
13 |
14 | func TestNewInvalidRowNumberError(t *testing.T) {
15 | assert.EqualError(t, newInvalidRowNumberError(0), "invalid row number 0")
16 | }
17 |
18 | func TestNewInvalidCellNameError(t *testing.T) {
19 | assert.EqualError(t, newInvalidCellNameError("A"), "invalid cell name \"A\"")
20 | assert.EqualError(t, newInvalidCellNameError(""), "invalid cell name \"\"")
21 | }
22 |
23 | func TestNewInvalidExcelDateError(t *testing.T) {
24 | assert.EqualError(t, newInvalidExcelDateError(-1), "invalid date value -1.000000, negative values are not supported")
25 | }
26 |
--------------------------------------------------------------------------------
/file.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 - 2025 The excelize Authors. All rights reserved. Use of
2 | // this source code is governed by a BSD-style license that can be found in
3 | // the LICENSE file.
4 | //
5 | // Package excelize providing a set of functions that allow you to write to and
6 | // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
7 | // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
8 | // Supports complex components by high compatibility, and provided streaming
9 | // API for generating or reading data from a worksheet with huge amounts of
10 | // data. This library needs Go version 1.23 or later.
11 |
12 | package excelize
13 |
14 | import (
15 | "archive/zip"
16 | "bytes"
17 | "encoding/binary"
18 | "encoding/xml"
19 | "io"
20 | "math"
21 | "os"
22 | "path/filepath"
23 | "sort"
24 | "strings"
25 | "sync"
26 | )
27 |
28 | // NewFile provides a function to create new file by default template.
29 | // For example:
30 | //
31 | // f := NewFile()
32 | func NewFile(opts ...Options) *File {
33 | f := newFile()
34 | f.Pkg.Store("_rels/.rels", []byte(xml.Header+templateRels))
35 | f.Pkg.Store(defaultXMLPathDocPropsApp, []byte(xml.Header+templateDocpropsApp))
36 | f.Pkg.Store(defaultXMLPathDocPropsCore, []byte(xml.Header+templateDocpropsCore))
37 | f.Pkg.Store(defaultXMLPathWorkbookRels, []byte(xml.Header+templateWorkbookRels))
38 | f.Pkg.Store("xl/theme/theme1.xml", []byte(xml.Header+templateTheme))
39 | f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(xml.Header+templateSheet))
40 | f.Pkg.Store(defaultXMLPathStyles, []byte(xml.Header+templateStyles))
41 | f.Pkg.Store(defaultXMLPathWorkbook, []byte(xml.Header+templateWorkbook))
42 | f.Pkg.Store(defaultXMLPathContentTypes, []byte(xml.Header+templateContentTypes))
43 | f.SheetCount = 1
44 | f.CalcChain, _ = f.calcChainReader()
45 | f.ContentTypes, _ = f.contentTypesReader()
46 | f.Styles, _ = f.stylesReader()
47 | f.WorkBook, _ = f.workbookReader()
48 | f.Relationships = sync.Map{}
49 | rels, _ := f.relsReader(defaultXMLPathWorkbookRels)
50 | f.Relationships.Store(defaultXMLPathWorkbookRels, rels)
51 | f.sheetMap["Sheet1"] = "xl/worksheets/sheet1.xml"
52 | ws, _ := f.workSheetReader("Sheet1")
53 | f.Sheet.Store("xl/worksheets/sheet1.xml", ws)
54 | f.Theme, _ = f.themeReader()
55 | f.options = f.getOptions(opts...)
56 | return f
57 | }
58 |
59 | // Save provides a function to override the spreadsheet with origin path.
60 | func (f *File) Save(opts ...Options) error {
61 | if f.Path == "" {
62 | return ErrSave
63 | }
64 | for i := range opts {
65 | f.options = &opts[i]
66 | }
67 | return f.SaveAs(f.Path, *f.options)
68 | }
69 |
70 | // SaveAs provides a function to create or update to a spreadsheet at the
71 | // provided path.
72 | func (f *File) SaveAs(name string, opts ...Options) error {
73 | if len(name) > MaxFilePathLength {
74 | return ErrMaxFilePathLength
75 | }
76 | f.Path = name
77 | if _, ok := supportedContentTypes[strings.ToLower(filepath.Ext(f.Path))]; !ok {
78 | return ErrWorkbookFileFormat
79 | }
80 | file, err := os.OpenFile(filepath.Clean(name), os.O_WRONLY|os.O_TRUNC|os.O_CREATE, os.ModePerm)
81 | if err != nil {
82 | return err
83 | }
84 | defer file.Close()
85 | return f.Write(file, opts...)
86 | }
87 |
88 | // Close closes and cleanup the open temporary file for the spreadsheet.
89 | func (f *File) Close() error {
90 | var firstErr error
91 | if f.sharedStringTemp != nil {
92 | firstErr = f.sharedStringTemp.Close()
93 | f.sharedStringTemp = nil
94 | }
95 | for _, stream := range f.streams {
96 | _ = stream.rawData.Close()
97 | }
98 | f.streams = nil
99 | f.tempFiles.Range(func(k, v interface{}) bool {
100 | if err := os.Remove(v.(string)); err != nil && firstErr == nil {
101 | firstErr = err
102 | }
103 | return true
104 | })
105 | f.tempFiles.Clear()
106 | return firstErr
107 | }
108 |
109 | // Write provides a function to write to an io.Writer.
110 | func (f *File) Write(w io.Writer, opts ...Options) error {
111 | _, err := f.WriteTo(w, opts...)
112 | return err
113 | }
114 |
115 | // WriteTo implements io.WriterTo to write the file.
116 | func (f *File) WriteTo(w io.Writer, opts ...Options) (int64, error) {
117 | for i := range opts {
118 | f.options = &opts[i]
119 | }
120 | if len(f.Path) != 0 {
121 | contentType, ok := supportedContentTypes[strings.ToLower(filepath.Ext(f.Path))]
122 | if !ok {
123 | return 0, ErrWorkbookFileFormat
124 | }
125 | if err := f.setContentTypePartProjectExtensions(contentType); err != nil {
126 | return 0, err
127 | }
128 | }
129 | buf, err := f.WriteToBuffer()
130 | if err != nil {
131 | return 0, err
132 | }
133 | return buf.WriteTo(w)
134 | }
135 |
136 | // WriteToBuffer provides a function to get bytes.Buffer from the saved file,
137 | // and it allocates space in memory. Be careful when the file size is large.
138 | func (f *File) WriteToBuffer() (*bytes.Buffer, error) {
139 | buf := new(bytes.Buffer)
140 | zw := zip.NewWriter(buf)
141 |
142 | if err := f.writeToZip(zw); err != nil {
143 | _ = zw.Close()
144 | return buf, err
145 | }
146 | if err := zw.Close(); err != nil {
147 | return buf, err
148 | }
149 | f.writeZip64LFH(buf)
150 | if f.options != nil && f.options.Password != "" {
151 | b, err := Encrypt(buf.Bytes(), f.options)
152 | if err != nil {
153 | return buf, err
154 | }
155 | buf.Reset()
156 | buf.Write(b)
157 | }
158 | return buf, nil
159 | }
160 |
161 | // writeToZip provides a function to write to zip.Writer
162 | func (f *File) writeToZip(zw *zip.Writer) error {
163 | f.calcChainWriter()
164 | f.commentsWriter()
165 | f.contentTypesWriter()
166 | f.drawingsWriter()
167 | f.volatileDepsWriter()
168 | f.vmlDrawingWriter()
169 | f.workBookWriter()
170 | f.workSheetWriter()
171 | f.relsWriter()
172 | _ = f.sharedStringsLoader()
173 | f.sharedStringsWriter()
174 | f.styleSheetWriter()
175 | f.themeWriter()
176 |
177 | for path, stream := range f.streams {
178 | fi, err := zw.Create(path)
179 | if err != nil {
180 | return err
181 | }
182 | var from io.Reader
183 | if from, err = stream.rawData.Reader(); err != nil {
184 | _ = stream.rawData.Close()
185 | return err
186 | }
187 | written, err := io.Copy(fi, from)
188 | if err != nil {
189 | return err
190 | }
191 | if written > math.MaxUint32 {
192 | f.zip64Entries = append(f.zip64Entries, path)
193 | }
194 | }
195 | var (
196 | n int
197 | err error
198 | files, tempFiles []string
199 | )
200 | f.Pkg.Range(func(path, content interface{}) bool {
201 | if _, ok := f.streams[path.(string)]; ok {
202 | return true
203 | }
204 | files = append(files, path.(string))
205 | return true
206 | })
207 | sort.Sort(sort.Reverse(sort.StringSlice(files)))
208 | for _, path := range files {
209 | var fi io.Writer
210 | if fi, err = zw.Create(path); err != nil {
211 | break
212 | }
213 | content, _ := f.Pkg.Load(path)
214 | if n, err = fi.Write(content.([]byte)); int64(n) > math.MaxUint32 {
215 | f.zip64Entries = append(f.zip64Entries, path)
216 | }
217 | }
218 | f.tempFiles.Range(func(path, content interface{}) bool {
219 | if _, ok := f.Pkg.Load(path); ok {
220 | return true
221 | }
222 | tempFiles = append(tempFiles, path.(string))
223 | return true
224 | })
225 | sort.Sort(sort.Reverse(sort.StringSlice(tempFiles)))
226 | for _, path := range tempFiles {
227 | var fi io.Writer
228 | if fi, err = zw.Create(path); err != nil {
229 | break
230 | }
231 | if n, err = fi.Write(f.readBytes(path)); int64(n) > math.MaxUint32 {
232 | f.zip64Entries = append(f.zip64Entries, path)
233 | }
234 | }
235 | return err
236 | }
237 |
238 | // writeZip64LFH function sets the ZIP version to 0x2D (45) in the Local File
239 | // Header (LFH). Excel strictly enforces ZIP64 format validation rules. When any
240 | // file within the workbook (OCP) exceeds 4GB in size, the ZIP64 format must be
241 | // used according to the PKZIP specification. However, ZIP files generated using
242 | // Go's standard archive/zip library always set the version in the local file
243 | // header to 20 (ZIP version 2.0) by default, as defined in the internal
244 | // 'writeHeader' function during ZIP creation. The archive/zip package only sets
245 | // the 'ReaderVersion' to 45 (ZIP64 version 4.5) in the central directory for
246 | // entries larger than 4GB. This results in a version mismatch between the
247 | // central directory and the local file header. As a result, opening the
248 | // generated workbook with spreadsheet application will prompt file corruption.
249 | func (f *File) writeZip64LFH(buf *bytes.Buffer) error {
250 | if len(f.zip64Entries) == 0 {
251 | return nil
252 | }
253 | data, offset := buf.Bytes(), 0
254 | for offset < len(data) {
255 | idx := bytes.Index(data[offset:], []byte{0x50, 0x4b, 0x03, 0x04})
256 | if idx == -1 {
257 | break
258 | }
259 | idx += offset
260 | if idx+30 > len(data) {
261 | break
262 | }
263 | filenameLen := int(binary.LittleEndian.Uint16(data[idx+26 : idx+28]))
264 | if idx+30+filenameLen > len(data) {
265 | break
266 | }
267 | if inStrSlice(f.zip64Entries, string(data[idx+30:idx+30+filenameLen]), true) != -1 {
268 | binary.LittleEndian.PutUint16(data[idx+4:idx+6], 45)
269 | }
270 | offset = idx + 1
271 | }
272 | return nil
273 | }
274 |
--------------------------------------------------------------------------------
/file_test.go:
--------------------------------------------------------------------------------
1 | package excelize
2 |
3 | import (
4 | "bufio"
5 | "bytes"
6 | "encoding/binary"
7 | "math"
8 | "os"
9 | "path/filepath"
10 | "strconv"
11 | "strings"
12 | "sync"
13 | "testing"
14 |
15 | "github.com/stretchr/testify/assert"
16 | "github.com/stretchr/testify/require"
17 | )
18 |
19 | func BenchmarkWrite(b *testing.B) {
20 | const s = "This is test data"
21 | for i := 0; i < b.N; i++ {
22 | f := NewFile()
23 | for row := 1; row <= 10000; row++ {
24 | for col := 1; col <= 20; col++ {
25 | val, err := CoordinatesToCellName(col, row)
26 | if err != nil {
27 | b.Error(err)
28 | }
29 | if err := f.SetCellValue("Sheet1", val, s); err != nil {
30 | b.Error(err)
31 | }
32 | }
33 | }
34 | // Save spreadsheet by the given path.
35 | err := f.SaveAs("./test.xlsx")
36 | if err != nil {
37 | b.Error(err)
38 | }
39 | }
40 | }
41 |
42 | func TestWriteTo(t *testing.T) {
43 | // Test WriteToBuffer err
44 | {
45 | f, buf := File{Pkg: sync.Map{}}, bytes.Buffer{}
46 | f.Pkg.Store("/d/", []byte("s"))
47 | _, err := f.WriteTo(bufio.NewWriter(&buf))
48 | assert.EqualError(t, err, "zip: write to directory")
49 | f.Pkg.Delete("/d/")
50 | }
51 | // Test file path overflow
52 | {
53 | f, buf := File{Pkg: sync.Map{}}, bytes.Buffer{}
54 | const maxUint16 = 1<<16 - 1
55 | f.Pkg.Store(strings.Repeat("s", maxUint16+1), nil)
56 | _, err := f.WriteTo(bufio.NewWriter(&buf))
57 | assert.EqualError(t, err, "zip: FileHeader.Name too long")
58 | }
59 | // Test StreamsWriter err
60 | {
61 | f, buf := File{Pkg: sync.Map{}}, bytes.Buffer{}
62 | f.Pkg.Store("s", nil)
63 | f.streams = make(map[string]*StreamWriter)
64 | file, _ := os.Open("123")
65 | f.streams["s"] = &StreamWriter{rawData: bufferedWriter{tmp: file}}
66 | _, err := f.WriteTo(bufio.NewWriter(&buf))
67 | assert.Nil(t, err)
68 | }
69 | // Test write with temporary file
70 | {
71 | f, buf := File{tempFiles: sync.Map{}}, bytes.Buffer{}
72 | const maxUint16 = 1<<16 - 1
73 | f.tempFiles.Store("s", "")
74 | f.tempFiles.Store(strings.Repeat("s", maxUint16+1), "")
75 | _, err := f.WriteTo(bufio.NewWriter(&buf))
76 | assert.EqualError(t, err, "zip: FileHeader.Name too long")
77 | }
78 | // Test write with unsupported workbook file format
79 | {
80 | f, buf := File{Pkg: sync.Map{}}, bytes.Buffer{}
81 | f.Pkg.Store("/d", []byte("s"))
82 | f.Path = "Book1.xls"
83 | _, err := f.WriteTo(bufio.NewWriter(&buf))
84 | assert.EqualError(t, err, ErrWorkbookFileFormat.Error())
85 | }
86 | // Test write with unsupported charset content types.
87 | {
88 | f, buf := NewFile(), bytes.Buffer{}
89 | f.ContentTypes, f.Path = nil, filepath.Join("test", "TestWriteTo.xlsx")
90 | f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
91 | _, err := f.WriteTo(bufio.NewWriter(&buf))
92 | assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
93 | }
94 | }
95 |
96 | func TestClose(t *testing.T) {
97 | f := NewFile()
98 | f.tempFiles.Store("/d/", "/d/")
99 | require.Error(t, f.Close())
100 | }
101 |
102 | func TestZip64(t *testing.T) {
103 | f := NewFile()
104 | _, err := f.NewSheet("Sheet2")
105 | assert.NoError(t, err)
106 | sw, err := f.NewStreamWriter("Sheet1")
107 | assert.NoError(t, err)
108 | for r := range 131 {
109 | rowData := make([]interface{}, 1000)
110 | for c := range 1000 {
111 | rowData[c] = strings.Repeat("c", TotalCellChars)
112 | }
113 | cell, err := CoordinatesToCellName(1, r+1)
114 | assert.NoError(t, err)
115 | assert.NoError(t, sw.SetRow(cell, rowData))
116 | }
117 | assert.NoError(t, sw.Flush())
118 | assert.NoError(t, f.SaveAs(filepath.Join("test", "TestZip64.xlsx")))
119 | assert.NoError(t, f.Close())
120 |
121 | // Test with filename length overflow
122 | f = NewFile()
123 | f.zip64Entries = append(f.zip64Entries, defaultXMLPathSharedStrings)
124 | buf := new(bytes.Buffer)
125 | buf.Write([]byte{0x50, 0x4b, 0x03, 0x04})
126 | buf.Write(make([]byte, 20))
127 | assert.NoError(t, f.writeZip64LFH(buf))
128 |
129 | // Test with file header less than the required 30 for the fixed header part
130 | f = NewFile()
131 | f.zip64Entries = append(f.zip64Entries, defaultXMLPathSharedStrings)
132 | buf.Reset()
133 | buf.Write([]byte{0x50, 0x4b, 0x03, 0x04})
134 | buf.Write(make([]byte, 22))
135 | binary.Write(buf, binary.LittleEndian, uint16(10))
136 | buf.Write(make([]byte, 2))
137 | buf.WriteString("test")
138 | assert.NoError(t, f.writeZip64LFH(buf))
139 |
140 | t.Run("for_save_zip64_with_in_memory_file_over_4GB", func(t *testing.T) {
141 | // Test save workbook in ZIP64 format with in memory file with size over 4GB.
142 | f := NewFile()
143 | f.Sheet.Delete("xl/worksheets/sheet1.xml")
144 | f.Pkg.Store("xl/worksheets/sheet1.xml", make([]byte, math.MaxUint32+1))
145 | _, err := f.WriteToBuffer()
146 | assert.NoError(t, err)
147 | assert.NoError(t, f.Close())
148 | })
149 |
150 | t.Run("for_save_zip64_with_in_temporary_file_over_4GB", func(t *testing.T) {
151 | // Test save workbook in ZIP64 format with temporary file with size over 4GB.
152 | if os.Getenv("GITHUB_ACTIONS") == "true" {
153 | t.Skip()
154 | }
155 | f := NewFile()
156 | f.Pkg.Delete("xl/worksheets/sheet1.xml")
157 | f.Sheet.Delete("xl/worksheets/sheet1.xml")
158 | tmp, err := os.CreateTemp(os.TempDir(), "excelize-")
159 | assert.NoError(t, err)
160 | assert.NoError(t, tmp.Truncate(math.MaxUint32+1))
161 | f.tempFiles.Store("xl/worksheets/sheet1.xml", tmp.Name())
162 | assert.NoError(t, tmp.Close())
163 | _, err = f.WriteToBuffer()
164 | assert.NoError(t, err)
165 | assert.NoError(t, f.Close())
166 | })
167 | }
168 |
169 | func TestRemoveTempFiles(t *testing.T) {
170 | tmp, err := os.CreateTemp("", "excelize-*")
171 | if err != nil {
172 | t.Fatal(err)
173 | }
174 | tmpName := tmp.Name()
175 | tmp.Close()
176 | f := NewFile()
177 | // fill the tempFiles map with non-existing (erroring on Remove) "files"
178 | for i := 0; i < 1000; i++ {
179 | f.tempFiles.Store(strconv.Itoa(i), "/hopefully not existing")
180 | }
181 | f.tempFiles.Store("existing", tmpName)
182 |
183 | require.Error(t, f.Close())
184 | if _, err := os.Stat(tmpName); err == nil {
185 | t.Errorf("temp file %q still exist", tmpName)
186 | os.Remove(tmpName)
187 | }
188 | }
189 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/xuri/excelize/v2
2 |
3 | go 1.23.0
4 |
5 | require (
6 | github.com/richardlehane/mscfb v1.0.4
7 | github.com/stretchr/testify v1.10.0
8 | github.com/tiendc/go-deepcopy v1.6.0
9 | github.com/xuri/efp v0.0.1
10 | github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9
11 | golang.org/x/crypto v0.38.0
12 | golang.org/x/image v0.25.0
13 | golang.org/x/net v0.40.0
14 | golang.org/x/text v0.25.0
15 | )
16 |
17 | require (
18 | github.com/davecgh/go-spew v1.1.1 // indirect
19 | github.com/pmezard/go-difflib v1.0.0 // indirect
20 | github.com/richardlehane/msoleps v1.0.4 // indirect
21 | gopkg.in/yaml.v3 v3.0.1 // indirect
22 | )
23 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
5 | github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM=
6 | github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk=
7 | github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
8 | github.com/richardlehane/msoleps v1.0.4 h1:WuESlvhX3gH2IHcd8UqyCuFY5yiq/GR/yqaSM/9/g00=
9 | github.com/richardlehane/msoleps v1.0.4/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
10 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
11 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
12 | github.com/tiendc/go-deepcopy v1.6.0 h1:0UtfV/imoCwlLxVsyfUd4hNHnB3drXsfle+wzSCA5Wo=
13 | github.com/tiendc/go-deepcopy v1.6.0/go.mod h1:toXoeQoUqXOOS/X4sKuiAoSk6elIdqc0pN7MTgOOo2I=
14 | github.com/xuri/efp v0.0.1 h1:fws5Rv3myXyYni8uwj2qKjVaRP30PdjeYe2Y6FDsCL8=
15 | github.com/xuri/efp v0.0.1/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
16 | github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 h1:+C0TIdyyYmzadGaL/HBLbf3WdLgC29pgyhTjAT/0nuE=
17 | github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
18 | golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
19 | golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
20 | golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ=
21 | golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs=
22 | golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
23 | golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
24 | golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
25 | golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
26 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
27 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
28 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
29 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
30 |
--------------------------------------------------------------------------------
/hsl.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
2 | //
3 | // Redistribution and use in source and binary forms, with or without
4 | // modification, are permitted provided that the following conditions are
5 | // met:
6 | //
7 | // * Redistributions of source code must retain the above copyright
8 | // notice, this list of conditions and the following disclaimer.
9 | // * Redistributions in binary form must reproduce the above
10 | // copyright notice, this list of conditions and the following disclaimer
11 | // in the documentation and/or other materials provided with the
12 | // distribution.
13 | // * Neither the name of Google Inc. nor the names of its
14 | // contributors may be used to endorse or promote products derived from
15 | // this software without specific prior written permission.
16 | //
17 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
29 | package excelize
30 |
31 | import (
32 | "image/color"
33 | "math"
34 | )
35 |
36 | // HSLModel converts any color.Color to a HSL color.
37 | var HSLModel = color.ModelFunc(hslModel)
38 |
39 | // HSL represents a cylindrical coordinate of points in an RGB color model.
40 | //
41 | // Values are in the range 0 to 1.
42 | type HSL struct {
43 | H, S, L float64
44 | }
45 |
46 | // RGBA returns the alpha-premultiplied red, green, blue and alpha values
47 | // for the HSL.
48 | func (c HSL) RGBA() (uint32, uint32, uint32, uint32) {
49 | r, g, b := HSLToRGB(c.H, c.S, c.L)
50 | return uint32(r) * 0x101, uint32(g) * 0x101, uint32(b) * 0x101, 0xffff
51 | }
52 |
53 | // hslModel converts a color.Color to HSL.
54 | func hslModel(c color.Color) color.Color {
55 | if _, ok := c.(HSL); ok {
56 | return c
57 | }
58 | r, g, b, _ := c.RGBA()
59 | h, s, l := RGBToHSL(uint8(r>>8), uint8(g>>8), uint8(b>>8))
60 | return HSL{h, s, l}
61 | }
62 |
63 | // RGBToHSL converts an RGB triple to an HSL triple.
64 | func RGBToHSL(r, g, b uint8) (h, s, l float64) {
65 | fR := float64(r) / 255
66 | fG := float64(g) / 255
67 | fB := float64(b) / 255
68 | maxVal := math.Max(math.Max(fR, fG), fB)
69 | minVal := math.Min(math.Min(fR, fG), fB)
70 | l = (maxVal + minVal) / 2
71 | if maxVal == minVal {
72 | // Achromatic.
73 | h, s = 0, 0
74 | } else {
75 | // Chromatic.
76 | d := maxVal - minVal
77 | if l > 0.5 {
78 | s = d / (2.0 - maxVal - minVal)
79 | } else {
80 | s = d / (maxVal + minVal)
81 | }
82 | switch maxVal {
83 | case fR:
84 | h = (fG - fB) / d
85 | if fG < fB {
86 | h += 6
87 | }
88 | case fG:
89 | h = (fB-fR)/d + 2
90 | case fB:
91 | h = (fR-fG)/d + 4
92 | }
93 | h /= 6
94 | }
95 | return
96 | }
97 |
98 | // HSLToRGB converts an HSL triple to an RGB triple.
99 | func HSLToRGB(h, s, l float64) (r, g, b uint8) {
100 | var fR, fG, fB float64
101 | if s == 0 {
102 | fR, fG, fB = l, l, l
103 | } else {
104 | var q float64
105 | if l < 0.5 {
106 | q = l * (1 + s)
107 | } else {
108 | q = l + s - s*l
109 | }
110 | p := 2*l - q
111 | fR = hueToRGB(p, q, h+1.0/3)
112 | fG = hueToRGB(p, q, h)
113 | fB = hueToRGB(p, q, h-1.0/3)
114 | }
115 | r = uint8((fR * 255) + 0.5)
116 | g = uint8((fG * 255) + 0.5)
117 | b = uint8((fB * 255) + 0.5)
118 | return
119 | }
120 |
121 | // hueToRGB is a helper function for HSLToRGB.
122 | func hueToRGB(p, q, t float64) float64 {
123 | if t < 0 {
124 | t++
125 | }
126 | if t > 1 {
127 | t--
128 | }
129 | if t < 1.0/6 {
130 | return p + (q-p)*6*t
131 | }
132 | if t < 0.5 {
133 | return q
134 | }
135 | if t < 2.0/3 {
136 | return p + (q-p)*(2.0/3-t)*6
137 | }
138 | return p
139 | }
140 |
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qax-os/excelize/c5934ea0e7d0f5d049e131eb2f0d6f35563172c4/logo.png
--------------------------------------------------------------------------------
/merge.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 - 2025 The excelize Authors. All rights reserved. Use of
2 | // this source code is governed by a BSD-style license that can be found in
3 | // the LICENSE file.
4 | //
5 | // Package excelize providing a set of functions that allow you to write to and
6 | // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
7 | // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
8 | // Supports complex components by high compatibility, and provided streaming
9 | // API for generating or reading data from a worksheet with huge amounts of
10 | // data. This library needs Go version 1.23 or later.
11 |
12 | package excelize
13 |
14 | import "strings"
15 |
16 | // Rect gets merged cell rectangle coordinates sequence.
17 | func (mc *xlsxMergeCell) Rect() ([]int, error) {
18 | var err error
19 | if mc.rect == nil {
20 | mergedCellsRef := mc.Ref
21 | if !strings.Contains(mergedCellsRef, ":") {
22 | mergedCellsRef += ":" + mergedCellsRef
23 | }
24 | mc.rect, err = rangeRefToCoordinates(mergedCellsRef)
25 | }
26 | return mc.rect, err
27 | }
28 |
29 | // MergeCell provides a function to merge cells by given range reference and
30 | // sheet name. Merging cells only keeps the upper-left cell value, and
31 | // discards the other values. For example create a merged cell of D3:E9 on
32 | // Sheet1:
33 | //
34 | // err := f.MergeCell("Sheet1", "D3", "E9")
35 | //
36 | // If you create a merged cell that overlaps with another existing merged cell,
37 | // those merged cells that already exist will be removed. The cell references
38 | // tuple after merging in the following range will be: A1(x3,y1) D1(x2,y1)
39 | // A8(x3,y4) D8(x2,y4)
40 | //
41 | // B1(x1,y1) D1(x2,y1)
42 | // +------------------------+
43 | // | |
44 | // A4(x3,y3) | C4(x4,y3) |
45 | // +------------------------+ |
46 | // | | | |
47 | // | |B5(x1,y2) | D5(x2,y2)|
48 | // | +------------------------+
49 | // | |
50 | // |A8(x3,y4) C8(x4,y4)|
51 | // +------------------------+
52 | func (f *File) MergeCell(sheet, topLeftCell, bottomRightCell string) error {
53 | rect, err := rangeRefToCoordinates(topLeftCell + ":" + bottomRightCell)
54 | if err != nil {
55 | return err
56 | }
57 | // Correct the range reference, such correct C1:B3 to B1:C3.
58 | _ = sortCoordinates(rect)
59 |
60 | topLeftCell, _ = CoordinatesToCellName(rect[0], rect[1])
61 | bottomRightCell, _ = CoordinatesToCellName(rect[2], rect[3])
62 |
63 | ws, err := f.workSheetReader(sheet)
64 | if err != nil {
65 | return err
66 | }
67 | ws.mu.Lock()
68 | defer ws.mu.Unlock()
69 | for col := rect[0]; col <= rect[2]; col++ {
70 | for row := rect[1]; row <= rect[3]; row++ {
71 | if col == rect[0] && row == rect[1] {
72 | continue
73 | }
74 | ws.prepareSheetXML(col, row)
75 | c := &ws.SheetData.Row[row-1].C[col-1]
76 | c.setCellDefault("")
77 | _ = f.removeFormula(c, ws, sheet)
78 | }
79 | }
80 | ref := topLeftCell + ":" + bottomRightCell
81 | if ws.MergeCells != nil {
82 | ws.MergeCells.Cells = append(ws.MergeCells.Cells, &xlsxMergeCell{Ref: ref, rect: rect})
83 | } else {
84 | ws.MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: ref, rect: rect}}}
85 | }
86 | ws.MergeCells.Count = len(ws.MergeCells.Cells)
87 | return err
88 | }
89 |
90 | // UnmergeCell provides a function to unmerge a given range reference.
91 | // For example unmerge range reference D3:E9 on Sheet1:
92 | //
93 | // err := f.UnmergeCell("Sheet1", "D3", "E9")
94 | //
95 | // Attention: overlapped range will also be unmerged.
96 | func (f *File) UnmergeCell(sheet, topLeftCell, bottomRightCell string) error {
97 | ws, err := f.workSheetReader(sheet)
98 | if err != nil {
99 | return err
100 | }
101 | ws.mu.Lock()
102 | defer ws.mu.Unlock()
103 | rect1, err := rangeRefToCoordinates(topLeftCell + ":" + bottomRightCell)
104 | if err != nil {
105 | return err
106 | }
107 |
108 | // Correct the range reference, such correct C1:B3 to B1:C3.
109 | _ = sortCoordinates(rect1)
110 |
111 | // return nil since no MergeCells in the sheet
112 | if ws.MergeCells == nil {
113 | return nil
114 | }
115 | if err = f.mergeOverlapCells(ws); err != nil {
116 | return err
117 | }
118 | i := 0
119 | for _, mergeCell := range ws.MergeCells.Cells {
120 | if mergeCell == nil {
121 | continue
122 | }
123 | mergedCellsRef := mergeCell.Ref
124 | if !strings.Contains(mergedCellsRef, ":") {
125 | mergedCellsRef += ":" + mergedCellsRef
126 | }
127 | rect2, _ := rangeRefToCoordinates(mergedCellsRef)
128 | if isOverlap(rect1, rect2) {
129 | continue
130 | }
131 | ws.MergeCells.Cells[i] = mergeCell
132 | i++
133 | }
134 | ws.MergeCells.Cells = ws.MergeCells.Cells[:i]
135 | ws.MergeCells.Count = len(ws.MergeCells.Cells)
136 | if ws.MergeCells.Count == 0 {
137 | ws.MergeCells = nil
138 | }
139 | return nil
140 | }
141 |
142 | // GetMergeCells provides a function to get all merged cells from a specific
143 | // worksheet.
144 | func (f *File) GetMergeCells(sheet string) ([]MergeCell, error) {
145 | var mergeCells []MergeCell
146 | ws, err := f.workSheetReader(sheet)
147 | if err != nil {
148 | return mergeCells, err
149 | }
150 | if ws.MergeCells != nil {
151 | if err = f.mergeOverlapCells(ws); err != nil {
152 | return mergeCells, err
153 | }
154 | mergeCells = make([]MergeCell, 0, len(ws.MergeCells.Cells))
155 | for i := range ws.MergeCells.Cells {
156 | ref := ws.MergeCells.Cells[i].Ref
157 | cell := strings.Split(ref, ":")[0]
158 | val, _ := f.GetCellValue(sheet, cell)
159 | mergeCells = append(mergeCells, []string{ref, val})
160 | }
161 | }
162 | return mergeCells, err
163 | }
164 |
165 | // overlapRange calculate overlap range of merged cells, and returns max
166 | // column and rows of the range.
167 | func overlapRange(ws *xlsxWorksheet) (row, col int, err error) {
168 | var rect []int
169 | for _, mergeCell := range ws.MergeCells.Cells {
170 | if mergeCell == nil {
171 | continue
172 | }
173 | if rect, err = mergeCell.Rect(); err != nil {
174 | return
175 | }
176 | x1, y1, x2, y2 := rect[0], rect[1], rect[2], rect[3]
177 | if x1 > col {
178 | col = x1
179 | }
180 | if x2 > col {
181 | col = x2
182 | }
183 | if y1 > row {
184 | row = y1
185 | }
186 | if y2 > row {
187 | row = y2
188 | }
189 | }
190 | return
191 | }
192 |
193 | // flatMergedCells convert merged cells range reference to cell-matrix.
194 | func flatMergedCells(ws *xlsxWorksheet, matrix [][]*xlsxMergeCell) error {
195 | for i, cell := range ws.MergeCells.Cells {
196 | rect, err := cell.Rect()
197 | if err != nil {
198 | return err
199 | }
200 | x1, y1, x2, y2 := rect[0]-1, rect[1]-1, rect[2]-1, rect[3]-1
201 | var overlapCells []*xlsxMergeCell
202 | for x := x1; x <= x2; x++ {
203 | for y := y1; y <= y2; y++ {
204 | if matrix[x][y] != nil {
205 | overlapCells = append(overlapCells, matrix[x][y])
206 | }
207 | matrix[x][y] = cell
208 | }
209 | }
210 | if len(overlapCells) != 0 {
211 | newCell := cell
212 | for _, overlapCell := range overlapCells {
213 | newCell = mergeCell(cell, overlapCell)
214 | }
215 | newRect, _ := newCell.Rect()
216 | x1, y1, x2, y2 := newRect[0]-1, newRect[1]-1, newRect[2]-1, newRect[3]-1
217 | for x := x1; x <= x2; x++ {
218 | for y := y1; y <= y2; y++ {
219 | matrix[x][y] = newCell
220 | }
221 | }
222 | ws.MergeCells.Cells[i] = newCell
223 | }
224 | }
225 | return nil
226 | }
227 |
228 | // mergeOverlapCells merge overlap cells.
229 | func (f *File) mergeOverlapCells(ws *xlsxWorksheet) error {
230 | rows, cols, err := overlapRange(ws)
231 | if err != nil {
232 | return err
233 | }
234 | if rows == 0 || cols == 0 {
235 | return nil
236 | }
237 | matrix := make([][]*xlsxMergeCell, cols)
238 | for i := range matrix {
239 | matrix[i] = make([]*xlsxMergeCell, rows)
240 | }
241 | _ = flatMergedCells(ws, matrix)
242 | mergeCells := ws.MergeCells.Cells[:0]
243 | for _, cell := range ws.MergeCells.Cells {
244 | rect, _ := cell.Rect()
245 | x1, y1, x2, y2 := rect[0]-1, rect[1]-1, rect[2]-1, rect[3]-1
246 | if matrix[x1][y1] == cell {
247 | mergeCells = append(mergeCells, cell)
248 | for x := x1; x <= x2; x++ {
249 | for y := y1; y <= y2; y++ {
250 | matrix[x][y] = nil
251 | }
252 | }
253 | }
254 | }
255 | ws.MergeCells.Count, ws.MergeCells.Cells = len(mergeCells), mergeCells
256 | return nil
257 | }
258 |
259 | // mergeCell merge two cells.
260 | func mergeCell(cell1, cell2 *xlsxMergeCell) *xlsxMergeCell {
261 | rect1, _ := cell1.Rect()
262 | rect2, _ := cell2.Rect()
263 |
264 | if rect1[0] > rect2[0] {
265 | rect1[0], rect2[0] = rect2[0], rect1[0]
266 | }
267 |
268 | if rect1[2] < rect2[2] {
269 | rect1[2], rect2[2] = rect2[2], rect1[2]
270 | }
271 |
272 | if rect1[1] > rect2[1] {
273 | rect1[1], rect2[1] = rect2[1], rect1[1]
274 | }
275 |
276 | if rect1[3] < rect2[3] {
277 | rect1[3], rect2[3] = rect2[3], rect1[3]
278 | }
279 | topLeftCell, _ := CoordinatesToCellName(rect1[0], rect1[1])
280 | bottomRightCell, _ := CoordinatesToCellName(rect1[2], rect1[3])
281 | return &xlsxMergeCell{rect: rect1, Ref: topLeftCell + ":" + bottomRightCell}
282 | }
283 |
284 | // MergeCell define a merged cell data.
285 | // It consists of the following structure.
286 | // example: []string{"D4:E10", "cell value"}
287 | type MergeCell []string
288 |
289 | // GetCellValue returns merged cell value.
290 | func (m *MergeCell) GetCellValue() string {
291 | return (*m)[1]
292 | }
293 |
294 | // GetStartAxis returns the top left cell reference of merged range, for
295 | // example: "C2".
296 | func (m *MergeCell) GetStartAxis() string {
297 | return strings.Split((*m)[0], ":")[0]
298 | }
299 |
300 | // GetEndAxis returns the bottom right cell reference of merged range, for
301 | // example: "D4".
302 | func (m *MergeCell) GetEndAxis() string {
303 | coordinates := strings.Split((*m)[0], ":")
304 | if len(coordinates) == 2 {
305 | return coordinates[1]
306 | }
307 | return coordinates[0]
308 | }
309 |
--------------------------------------------------------------------------------
/merge_test.go:
--------------------------------------------------------------------------------
1 | package excelize
2 |
3 | import (
4 | "path/filepath"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestMergeCell(t *testing.T) {
11 | f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
12 | if !assert.NoError(t, err) {
13 | t.FailNow()
14 | }
15 | assert.EqualError(t, f.MergeCell("Sheet1", "A", "B"), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
16 | for _, cells := range [][]string{
17 | {"D9", "D9"},
18 | {"D9", "E9"},
19 | {"H14", "G13"},
20 | {"C9", "D8"},
21 | {"F11", "G13"},
22 | {"H7", "B15"},
23 | {"D11", "F13"},
24 | {"G10", "K12"},
25 | } {
26 | assert.NoError(t, f.MergeCell("Sheet1", cells[0], cells[1]))
27 | }
28 | assert.NoError(t, f.SetCellValue("Sheet1", "G11", "set value in merged cell"))
29 | assert.NoError(t, f.SetCellInt("Sheet1", "H11", 100))
30 | assert.NoError(t, f.SetCellValue("Sheet1", "I11", 0.5))
31 | assert.NoError(t, f.SetCellHyperLink("Sheet1", "J11", "https://github.com/xuri/excelize", "External"))
32 | assert.NoError(t, f.SetCellFormula("Sheet1", "G12", "SUM(Sheet1!B19,Sheet1!C19)"))
33 | value, err := f.GetCellValue("Sheet1", "H11")
34 | assert.Equal(t, "100", value)
35 | assert.NoError(t, err)
36 | // Merged cell ref is single coordinate
37 | value, err = f.GetCellValue("Sheet2", "A6")
38 | assert.Empty(t, value)
39 | assert.NoError(t, err)
40 | value, err = f.GetCellFormula("Sheet1", "G12")
41 | assert.Equal(t, "SUM(Sheet1!B19,Sheet1!C19)", value)
42 | assert.NoError(t, err)
43 |
44 | _, err = f.NewSheet("Sheet3")
45 | assert.NoError(t, err)
46 |
47 | for _, cells := range [][]string{
48 | {"D11", "F13"},
49 | {"G10", "K12"},
50 | {"B1", "D5"}, // B1:D5
51 | {"E1", "F5"}, // E1:F5
52 | {"H2", "I5"},
53 | {"I4", "J6"}, // H2:J6
54 | {"M2", "N5"},
55 | {"L4", "M6"}, // L2:N6
56 | {"P4", "Q7"},
57 | {"O2", "P5"}, // O2:Q7
58 | {"A9", "B12"},
59 | {"B7", "C9"}, // A7:C12
60 | {"E9", "F10"},
61 | {"D8", "G12"},
62 | {"I8", "I12"},
63 | {"I10", "K10"},
64 | {"M8", "Q13"},
65 | {"N10", "O11"},
66 | } {
67 | assert.NoError(t, f.MergeCell("Sheet3", cells[0], cells[1]))
68 | }
69 |
70 | // Test merge cells on not exists worksheet
71 | assert.EqualError(t, f.MergeCell("SheetN", "N10", "O11"), "sheet SheetN does not exist")
72 | // Test merged cells with invalid sheet name
73 | assert.EqualError(t, f.MergeCell("Sheet:1", "N10", "O11"), ErrSheetNameInvalid.Error())
74 | assert.NoError(t, f.SaveAs(filepath.Join("test", "TestMergeCell.xlsx")))
75 | assert.NoError(t, f.Close())
76 |
77 | f = NewFile()
78 | assert.NoError(t, f.MergeCell("Sheet1", "A2", "B3"))
79 | ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
80 | assert.True(t, ok)
81 | ws.(*xlsxWorksheet).MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{nil, nil}}
82 | assert.NoError(t, f.MergeCell("Sheet1", "A2", "B3"))
83 | // Test getting merged cells with the same start and end axis
84 | ws.(*xlsxWorksheet).MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A1"}}}
85 | mergedCells, err := f.GetMergeCells("Sheet1")
86 | assert.NoError(t, err)
87 | assert.Equal(t, "A1", mergedCells[0].GetStartAxis())
88 | assert.Equal(t, "A1", mergedCells[0].GetEndAxis())
89 | assert.Empty(t, mergedCells[0].GetCellValue())
90 | }
91 |
92 | func TestMergeCellOverlap(t *testing.T) {
93 | f := NewFile()
94 | assert.NoError(t, f.MergeCell("Sheet1", "A1", "C2"))
95 | assert.NoError(t, f.MergeCell("Sheet1", "B2", "D3"))
96 | assert.NoError(t, f.SaveAs(filepath.Join("test", "TestMergeCellOverlap.xlsx")))
97 |
98 | f, err := OpenFile(filepath.Join("test", "TestMergeCellOverlap.xlsx"))
99 | if !assert.NoError(t, err) {
100 | t.FailNow()
101 | }
102 | mc, err := f.GetMergeCells("Sheet1")
103 | assert.NoError(t, err)
104 | assert.Len(t, mc, 1)
105 | assert.Equal(t, "A1", mc[0].GetStartAxis())
106 | assert.Equal(t, "D3", mc[0].GetEndAxis())
107 | assert.Empty(t, mc[0].GetCellValue())
108 | assert.NoError(t, f.Close())
109 | }
110 |
111 | func TestGetMergeCells(t *testing.T) {
112 | wants := []struct {
113 | value string
114 | start string
115 | end string
116 | }{{
117 | value: "A1",
118 | start: "A1",
119 | end: "B1",
120 | }, {
121 | value: "A2",
122 | start: "A2",
123 | end: "A3",
124 | }, {
125 | value: "A4",
126 | start: "A4",
127 | end: "B5",
128 | }, {
129 | value: "A7",
130 | start: "A7",
131 | end: "C10",
132 | }}
133 |
134 | f, err := OpenFile(filepath.Join("test", "MergeCell.xlsx"))
135 | if !assert.NoError(t, err) {
136 | t.FailNow()
137 | }
138 | sheet1 := f.GetSheetName(0)
139 |
140 | mergeCells, err := f.GetMergeCells(sheet1)
141 | if !assert.Len(t, mergeCells, len(wants)) {
142 | t.FailNow()
143 | }
144 | assert.NoError(t, err)
145 |
146 | for i, m := range mergeCells {
147 | assert.Equal(t, wants[i].value, m.GetCellValue())
148 | assert.Equal(t, wants[i].start, m.GetStartAxis())
149 | assert.Equal(t, wants[i].end, m.GetEndAxis())
150 | }
151 | // Test get merged cells with invalid sheet name
152 | _, err = f.GetMergeCells("Sheet:1")
153 | assert.EqualError(t, err, ErrSheetNameInvalid.Error())
154 | // Test get merged cells on not exists worksheet
155 | _, err = f.GetMergeCells("SheetN")
156 | assert.EqualError(t, err, "sheet SheetN does not exist")
157 | assert.NoError(t, f.Close())
158 | }
159 |
160 | func TestUnmergeCell(t *testing.T) {
161 | f, err := OpenFile(filepath.Join("test", "MergeCell.xlsx"))
162 | if !assert.NoError(t, err) {
163 | t.FailNow()
164 | }
165 | sheet1 := f.GetSheetName(0)
166 |
167 | sheet, err := f.workSheetReader(sheet1)
168 | assert.NoError(t, err)
169 |
170 | mergeCellNum := len(sheet.MergeCells.Cells)
171 |
172 | assert.EqualError(t, f.UnmergeCell("Sheet1", "A", "A"), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
173 |
174 | // Test unmerge the merged cells that contains A1
175 | assert.NoError(t, f.UnmergeCell(sheet1, "A1", "A1"))
176 | if len(sheet.MergeCells.Cells) != mergeCellNum-1 {
177 | t.FailNow()
178 | }
179 |
180 | assert.NoError(t, f.SaveAs(filepath.Join("test", "TestUnmergeCell.xlsx")))
181 | assert.NoError(t, f.Close())
182 |
183 | f = NewFile()
184 | assert.NoError(t, f.MergeCell("Sheet1", "A2", "B3"))
185 | // Test unmerged range reference on not exists worksheet
186 | assert.EqualError(t, f.UnmergeCell("SheetN", "A1", "A1"), "sheet SheetN does not exist")
187 |
188 | // Test unmerge the merged cells with invalid sheet name
189 | assert.EqualError(t, f.UnmergeCell("Sheet:1", "A1", "A1"), ErrSheetNameInvalid.Error())
190 |
191 | ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
192 | assert.True(t, ok)
193 | ws.(*xlsxWorksheet).MergeCells = nil
194 | assert.NoError(t, f.UnmergeCell("Sheet1", "H7", "B15"))
195 |
196 | ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml")
197 | assert.True(t, ok)
198 | ws.(*xlsxWorksheet).MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{nil, nil}}
199 | assert.NoError(t, f.UnmergeCell("Sheet1", "H15", "B7"))
200 |
201 | ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml")
202 | assert.True(t, ok)
203 | ws.(*xlsxWorksheet).MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A1"}}}
204 | assert.NoError(t, f.UnmergeCell("Sheet1", "A2", "B3"))
205 |
206 | ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml")
207 | assert.True(t, ok)
208 | ws.(*xlsxWorksheet).MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:A"}}}
209 | assert.EqualError(t, f.UnmergeCell("Sheet1", "A2", "B3"), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
210 | }
211 |
212 | func TestFlatMergedCells(t *testing.T) {
213 | ws := &xlsxWorksheet{MergeCells: &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: ""}}}}
214 | assert.EqualError(t, flatMergedCells(ws, [][]*xlsxMergeCell{}), "cannot convert cell \"\" to coordinates: invalid cell name \"\"")
215 | }
216 |
217 | func TestMergeCellsParser(t *testing.T) {
218 | ws := &xlsxWorksheet{MergeCells: &xlsxMergeCells{Cells: []*xlsxMergeCell{nil}}}
219 | _, err := ws.mergeCellsParser("A1")
220 | assert.NoError(t, err)
221 | }
222 |
--------------------------------------------------------------------------------
/shape_test.go:
--------------------------------------------------------------------------------
1 | package excelize
2 |
3 | import (
4 | "path/filepath"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestAddShape(t *testing.T) {
11 | f, err := prepareTestBook1()
12 | if !assert.NoError(t, err) {
13 | t.FailNow()
14 | }
15 | assert.NoError(t, f.AddShape("Sheet1", &Shape{
16 | Cell: "A30",
17 | Type: "rect",
18 | Paragraph: []RichTextRun{
19 | {Text: "Rectangle", Font: &Font{Color: "CD5C5C"}},
20 | {Text: "Shape", Font: &Font{Bold: true, Color: "2980B9"}},
21 | },
22 | }))
23 | assert.NoError(t, f.AddShape("Sheet1", &Shape{Cell: "B30", Type: "rect", Paragraph: []RichTextRun{{Text: "Rectangle"}, {}}}))
24 | assert.NoError(t, f.AddShape("Sheet1", &Shape{Cell: "C30", Type: "rect"}))
25 | assert.EqualError(t, f.AddShape("Sheet3",
26 | &Shape{
27 | Cell: "H1",
28 | Type: "ellipseRibbon",
29 | Line: ShapeLine{Color: "4286F4"},
30 | Fill: Fill{Color: []string{"8EB9FF"}},
31 | Paragraph: []RichTextRun{
32 | {
33 | Font: &Font{
34 | Bold: true,
35 | Italic: true,
36 | Family: "Times New Roman",
37 | Size: 36,
38 | Color: "777777",
39 | Underline: "single",
40 | },
41 | },
42 | },
43 | },
44 | ), "sheet Sheet3 does not exist")
45 | assert.Equal(t, ErrParameterInvalid, f.AddShape("Sheet3", nil))
46 | assert.Equal(t, ErrParameterInvalid, f.AddShape("Sheet1", &Shape{Cell: "A1"}))
47 | assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), f.AddShape("Sheet1", &Shape{
48 | Cell: "A",
49 | Type: "rect",
50 | Paragraph: []RichTextRun{
51 | {Text: "Rectangle", Font: &Font{Color: "CD5C5C"}},
52 | {Text: "Shape", Font: &Font{Bold: true, Color: "2980B9"}},
53 | },
54 | }))
55 | assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddShape1.xlsx")))
56 |
57 | // Test add first shape for given sheet
58 | f = NewFile()
59 | lineWidth := 1.2
60 | assert.NoError(t, f.AddShape("Sheet1",
61 | &Shape{
62 | Cell: "A1",
63 | Type: "ellipseRibbon",
64 | Line: ShapeLine{Color: "4286F4", Width: &lineWidth},
65 | Fill: Fill{Color: []string{"8EB9FF"}},
66 | Paragraph: []RichTextRun{
67 | {
68 | Font: &Font{
69 | Bold: true,
70 | Italic: true,
71 | Family: "Times New Roman",
72 | Size: 36,
73 | Color: "777777",
74 | Underline: "single",
75 | },
76 | },
77 | },
78 | Height: 90,
79 | }))
80 | assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddShape2.xlsx")))
81 | // Test add shape with invalid sheet name
82 | assert.Equal(t, ErrSheetNameInvalid, f.AddShape("Sheet:1", &Shape{
83 | Cell: "A30",
84 | Type: "rect",
85 | Paragraph: []RichTextRun{
86 | {Text: "Rectangle", Font: &Font{Color: "CD5C5C"}},
87 | {Text: "Shape", Font: &Font{Bold: true, Color: "2980B9"}},
88 | },
89 | }))
90 | // Test add shape with unsupported charset style sheet
91 | f.Styles = nil
92 | f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
93 | assert.EqualError(t, f.AddShape("Sheet1", &Shape{Cell: "B30", Type: "rect", Paragraph: []RichTextRun{{Text: "Rectangle"}, {}}}), "XML syntax error on line 1: invalid UTF-8")
94 | // Test add shape with unsupported charset content types
95 | f = NewFile()
96 | f.ContentTypes = nil
97 | f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
98 | assert.EqualError(t, f.AddShape("Sheet1", &Shape{Cell: "B30", Type: "rect", Paragraph: []RichTextRun{{Text: "Rectangle"}, {}}}), "XML syntax error on line 1: invalid UTF-8")
99 | }
100 |
101 | func TestAddDrawingShape(t *testing.T) {
102 | f := NewFile()
103 | path := "xl/drawings/drawing1.xml"
104 | f.Pkg.Store(path, MacintoshCyrillicCharset)
105 | assert.EqualError(t, f.addDrawingShape("sheet1", path, "A1",
106 | &Shape{
107 | Width: defaultShapeSize,
108 | Height: defaultShapeSize,
109 | Format: GraphicOptions{
110 | PrintObject: boolPtr(true),
111 | Locked: boolPtr(false),
112 | },
113 | },
114 | ), "XML syntax error on line 1: invalid UTF-8")
115 | }
116 |
--------------------------------------------------------------------------------
/sheetpr.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 - 2025 The excelize Authors. All rights reserved. Use of
2 | // this source code is governed by a BSD-style license that can be found in
3 | // the LICENSE file.
4 | //
5 | // Package excelize providing a set of functions that allow you to write to and
6 | // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
7 | // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
8 | // Supports complex components by high compatibility, and provided streaming
9 | // API for generating or reading data from a worksheet with huge amounts of
10 | // data. This library needs Go version 1.23 or later.
11 |
12 | package excelize
13 |
14 | import "reflect"
15 |
16 | // SetPageMargins provides a function to set worksheet page margins.
17 | func (f *File) SetPageMargins(sheet string, opts *PageLayoutMarginsOptions) error {
18 | ws, err := f.workSheetReader(sheet)
19 | if err != nil {
20 | return err
21 | }
22 | if opts == nil {
23 | return err
24 | }
25 | preparePageMargins := func(ws *xlsxWorksheet) {
26 | if ws.PageMargins == nil {
27 | ws.PageMargins = new(xlsxPageMargins)
28 | }
29 | }
30 | preparePrintOptions := func(ws *xlsxWorksheet) {
31 | if ws.PrintOptions == nil {
32 | ws.PrintOptions = new(xlsxPrintOptions)
33 | }
34 | }
35 | s := reflect.ValueOf(opts).Elem()
36 | for i := 0; i < 6; i++ {
37 | if !s.Field(i).IsNil() {
38 | preparePageMargins(ws)
39 | name := s.Type().Field(i).Name
40 | reflect.ValueOf(ws.PageMargins).Elem().FieldByName(name).Set(s.Field(i).Elem())
41 | }
42 | }
43 | if opts.Horizontally != nil {
44 | preparePrintOptions(ws)
45 | ws.PrintOptions.HorizontalCentered = *opts.Horizontally
46 | }
47 | if opts.Vertically != nil {
48 | preparePrintOptions(ws)
49 | ws.PrintOptions.VerticalCentered = *opts.Vertically
50 | }
51 | return err
52 | }
53 |
54 | // GetPageMargins provides a function to get worksheet page margins.
55 | func (f *File) GetPageMargins(sheet string) (PageLayoutMarginsOptions, error) {
56 | opts := PageLayoutMarginsOptions{
57 | Bottom: float64Ptr(0.75),
58 | Footer: float64Ptr(0.3),
59 | Header: float64Ptr(0.3),
60 | Left: float64Ptr(0.7),
61 | Right: float64Ptr(0.7),
62 | Top: float64Ptr(0.75),
63 | }
64 | ws, err := f.workSheetReader(sheet)
65 | if err != nil {
66 | return opts, err
67 | }
68 | if ws.PageMargins != nil {
69 | opts.Bottom = float64Ptr(ws.PageMargins.Bottom)
70 | opts.Footer = float64Ptr(ws.PageMargins.Footer)
71 | opts.Header = float64Ptr(ws.PageMargins.Header)
72 | opts.Left = float64Ptr(ws.PageMargins.Left)
73 | opts.Right = float64Ptr(ws.PageMargins.Right)
74 | opts.Top = float64Ptr(ws.PageMargins.Top)
75 | }
76 | if ws.PrintOptions != nil {
77 | opts.Horizontally = boolPtr(ws.PrintOptions.HorizontalCentered)
78 | opts.Vertically = boolPtr(ws.PrintOptions.VerticalCentered)
79 | }
80 | return opts, err
81 | }
82 |
83 | // prepareSheetPr create sheetPr element which not exist.
84 | func (ws *xlsxWorksheet) prepareSheetPr() {
85 | if ws.SheetPr == nil {
86 | ws.SheetPr = new(xlsxSheetPr)
87 | }
88 | }
89 |
90 | // setSheetOutlineProps set worksheet outline properties by given options.
91 | func (ws *xlsxWorksheet) setSheetOutlineProps(opts *SheetPropsOptions) {
92 | prepareOutlinePr := func(ws *xlsxWorksheet) {
93 | ws.prepareSheetPr()
94 | if ws.SheetPr.OutlinePr == nil {
95 | ws.SheetPr.OutlinePr = new(xlsxOutlinePr)
96 | }
97 | }
98 | if opts.OutlineSummaryBelow != nil {
99 | prepareOutlinePr(ws)
100 | ws.SheetPr.OutlinePr.SummaryBelow = opts.OutlineSummaryBelow
101 | }
102 | if opts.OutlineSummaryRight != nil {
103 | prepareOutlinePr(ws)
104 | ws.SheetPr.OutlinePr.SummaryRight = opts.OutlineSummaryRight
105 | }
106 | }
107 |
108 | // setSheetProps set worksheet format properties by given options.
109 | func (ws *xlsxWorksheet) setSheetProps(opts *SheetPropsOptions) {
110 | preparePageSetUpPr := func(ws *xlsxWorksheet) {
111 | ws.prepareSheetPr()
112 | if ws.SheetPr.PageSetUpPr == nil {
113 | ws.SheetPr.PageSetUpPr = new(xlsxPageSetUpPr)
114 | }
115 | }
116 | prepareTabColor := func(ws *xlsxWorksheet) {
117 | ws.prepareSheetPr()
118 | if ws.SheetPr.TabColor == nil {
119 | ws.SheetPr.TabColor = new(xlsxColor)
120 | }
121 | }
122 | if opts.CodeName != nil {
123 | ws.prepareSheetPr()
124 | ws.SheetPr.CodeName = *opts.CodeName
125 | }
126 | if opts.EnableFormatConditionsCalculation != nil {
127 | ws.prepareSheetPr()
128 | ws.SheetPr.EnableFormatConditionsCalculation = opts.EnableFormatConditionsCalculation
129 | }
130 | if opts.Published != nil {
131 | ws.prepareSheetPr()
132 | ws.SheetPr.Published = opts.Published
133 | }
134 | if opts.AutoPageBreaks != nil {
135 | preparePageSetUpPr(ws)
136 | ws.SheetPr.PageSetUpPr.AutoPageBreaks = *opts.AutoPageBreaks
137 | }
138 | if opts.FitToPage != nil {
139 | preparePageSetUpPr(ws)
140 | ws.SheetPr.PageSetUpPr.FitToPage = *opts.FitToPage
141 | }
142 | ws.setSheetOutlineProps(opts)
143 | s := reflect.ValueOf(opts).Elem()
144 | for i := 5; i < 9; i++ {
145 | if !s.Field(i).IsNil() {
146 | prepareTabColor(ws)
147 | name := s.Type().Field(i).Name
148 | fld := reflect.ValueOf(ws.SheetPr.TabColor).Elem().FieldByName(name[8:])
149 | if s.Field(i).Kind() == reflect.Ptr && fld.Kind() == reflect.Ptr {
150 | fld.Set(s.Field(i))
151 | continue
152 | }
153 | fld.Set(s.Field(i).Elem())
154 | }
155 | }
156 | }
157 |
158 | // SetSheetProps provides a function to set worksheet properties.
159 | func (f *File) SetSheetProps(sheet string, opts *SheetPropsOptions) error {
160 | ws, err := f.workSheetReader(sheet)
161 | if err != nil {
162 | return err
163 | }
164 | if opts == nil {
165 | return err
166 | }
167 | ws.setSheetProps(opts)
168 | if ws.SheetFormatPr == nil {
169 | ws.SheetFormatPr = &xlsxSheetFormatPr{DefaultRowHeight: defaultRowHeight}
170 | }
171 | s := reflect.ValueOf(opts).Elem()
172 | for i := 11; i < 18; i++ {
173 | if !s.Field(i).IsNil() {
174 | name := s.Type().Field(i).Name
175 | reflect.ValueOf(ws.SheetFormatPr).Elem().FieldByName(name).Set(s.Field(i).Elem())
176 | }
177 | }
178 | return err
179 | }
180 |
181 | // GetSheetProps provides a function to get worksheet properties.
182 | func (f *File) GetSheetProps(sheet string) (SheetPropsOptions, error) {
183 | baseColWidth := uint8(8)
184 | opts := SheetPropsOptions{
185 | EnableFormatConditionsCalculation: boolPtr(true),
186 | Published: boolPtr(true),
187 | AutoPageBreaks: boolPtr(true),
188 | OutlineSummaryBelow: boolPtr(true),
189 | BaseColWidth: &baseColWidth,
190 | }
191 | ws, err := f.workSheetReader(sheet)
192 | if err != nil {
193 | return opts, err
194 | }
195 | if ws.SheetPr != nil {
196 | opts.CodeName = stringPtr(ws.SheetPr.CodeName)
197 | if ws.SheetPr.EnableFormatConditionsCalculation != nil {
198 | opts.EnableFormatConditionsCalculation = ws.SheetPr.EnableFormatConditionsCalculation
199 | }
200 | if ws.SheetPr.Published != nil {
201 | opts.Published = ws.SheetPr.Published
202 | }
203 | if ws.SheetPr.PageSetUpPr != nil {
204 | opts.AutoPageBreaks = boolPtr(ws.SheetPr.PageSetUpPr.AutoPageBreaks)
205 | opts.FitToPage = boolPtr(ws.SheetPr.PageSetUpPr.FitToPage)
206 | }
207 | if ws.SheetPr.OutlinePr != nil {
208 | opts.OutlineSummaryBelow = ws.SheetPr.OutlinePr.SummaryBelow
209 | opts.OutlineSummaryRight = ws.SheetPr.OutlinePr.SummaryRight
210 | }
211 | if ws.SheetPr.TabColor != nil {
212 | opts.TabColorIndexed = intPtr(ws.SheetPr.TabColor.Indexed)
213 | opts.TabColorRGB = stringPtr(ws.SheetPr.TabColor.RGB)
214 | opts.TabColorTheme = ws.SheetPr.TabColor.Theme
215 | opts.TabColorTint = float64Ptr(ws.SheetPr.TabColor.Tint)
216 | }
217 | }
218 | if ws.SheetFormatPr != nil {
219 | opts.BaseColWidth = &ws.SheetFormatPr.BaseColWidth
220 | opts.DefaultColWidth = float64Ptr(ws.SheetFormatPr.DefaultColWidth)
221 | opts.DefaultRowHeight = float64Ptr(ws.SheetFormatPr.DefaultRowHeight)
222 | opts.CustomHeight = boolPtr(ws.SheetFormatPr.CustomHeight)
223 | opts.ZeroHeight = boolPtr(ws.SheetFormatPr.ZeroHeight)
224 | opts.ThickTop = boolPtr(ws.SheetFormatPr.ThickTop)
225 | opts.ThickBottom = boolPtr(ws.SheetFormatPr.ThickBottom)
226 | }
227 | return opts, err
228 | }
229 |
--------------------------------------------------------------------------------
/sheetpr_test.go:
--------------------------------------------------------------------------------
1 | package excelize
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestSetPageMargins(t *testing.T) {
10 | f := NewFile()
11 | assert.NoError(t, f.SetPageMargins("Sheet1", nil))
12 | ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
13 | assert.True(t, ok)
14 | ws.(*xlsxWorksheet).PageMargins = nil
15 | ws.(*xlsxWorksheet).PrintOptions = nil
16 | expected := PageLayoutMarginsOptions{
17 | Bottom: float64Ptr(1.0),
18 | Footer: float64Ptr(1.0),
19 | Header: float64Ptr(1.0),
20 | Left: float64Ptr(1.0),
21 | Right: float64Ptr(1.0),
22 | Top: float64Ptr(1.0),
23 | Horizontally: boolPtr(true),
24 | Vertically: boolPtr(true),
25 | }
26 | assert.NoError(t, f.SetPageMargins("Sheet1", &expected))
27 | opts, err := f.GetPageMargins("Sheet1")
28 | assert.NoError(t, err)
29 | assert.Equal(t, expected, opts)
30 | // Test set page margins on not exists worksheet
31 | assert.EqualError(t, f.SetPageMargins("SheetN", nil), "sheet SheetN does not exist")
32 | // Test set page margins with invalid sheet name
33 | assert.Equal(t, ErrSheetNameInvalid, f.SetPageMargins("Sheet:1", nil))
34 | }
35 |
36 | func TestGetPageMargins(t *testing.T) {
37 | f := NewFile()
38 | // Test get page margins on not exists worksheet
39 | _, err := f.GetPageMargins("SheetN")
40 | assert.EqualError(t, err, "sheet SheetN does not exist")
41 | // Test get page margins with invalid sheet name
42 | _, err = f.GetPageMargins("Sheet:1")
43 | assert.Equal(t, ErrSheetNameInvalid, err)
44 | }
45 |
46 | func TestSetSheetProps(t *testing.T) {
47 | f := NewFile()
48 | assert.NoError(t, f.SetSheetProps("Sheet1", nil))
49 | ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
50 | assert.True(t, ok)
51 | ws.(*xlsxWorksheet).SheetPr = nil
52 | ws.(*xlsxWorksheet).SheetFormatPr = nil
53 | baseColWidth, enable := uint8(8), boolPtr(true)
54 | expected := SheetPropsOptions{
55 | CodeName: stringPtr("code"),
56 | EnableFormatConditionsCalculation: enable,
57 | Published: enable,
58 | AutoPageBreaks: enable,
59 | FitToPage: enable,
60 | TabColorIndexed: intPtr(1),
61 | TabColorRGB: stringPtr("FFFF00"),
62 | TabColorTheme: intPtr(1),
63 | TabColorTint: float64Ptr(1),
64 | OutlineSummaryBelow: enable,
65 | OutlineSummaryRight: enable,
66 | BaseColWidth: &baseColWidth,
67 | DefaultColWidth: float64Ptr(10),
68 | DefaultRowHeight: float64Ptr(10),
69 | CustomHeight: enable,
70 | ZeroHeight: enable,
71 | ThickTop: enable,
72 | ThickBottom: enable,
73 | }
74 | assert.NoError(t, f.SetSheetProps("Sheet1", &expected))
75 | opts, err := f.GetSheetProps("Sheet1")
76 | assert.NoError(t, err)
77 | assert.Equal(t, expected, opts)
78 |
79 | ws.(*xlsxWorksheet).SheetPr = nil
80 | assert.NoError(t, f.SetSheetProps("Sheet1", &SheetPropsOptions{FitToPage: enable}))
81 | ws.(*xlsxWorksheet).SheetPr = nil
82 | assert.NoError(t, f.SetSheetProps("Sheet1", &SheetPropsOptions{TabColorRGB: stringPtr("FFFF00")}))
83 | ws.(*xlsxWorksheet).SheetPr = nil
84 | assert.NoError(t, f.SetSheetProps("Sheet1", &SheetPropsOptions{TabColorTheme: intPtr(1)}))
85 | ws.(*xlsxWorksheet).SheetPr = nil
86 | assert.NoError(t, f.SetSheetProps("Sheet1", &SheetPropsOptions{TabColorTint: float64Ptr(1)}))
87 |
88 | // Test set worksheet properties on not exists worksheet
89 | assert.EqualError(t, f.SetSheetProps("SheetN", nil), "sheet SheetN does not exist")
90 | // Test set worksheet properties with invalid sheet name
91 | assert.Equal(t, ErrSheetNameInvalid, f.SetSheetProps("Sheet:1", nil))
92 | }
93 |
94 | func TestGetSheetProps(t *testing.T) {
95 | f := NewFile()
96 | // Test get worksheet properties on not exists worksheet
97 | _, err := f.GetSheetProps("SheetN")
98 | assert.EqualError(t, err, "sheet SheetN does not exist")
99 | // Test get worksheet properties with invalid sheet name
100 | _, err = f.GetSheetProps("Sheet:1")
101 | assert.Equal(t, ErrSheetNameInvalid, err)
102 | }
103 |
--------------------------------------------------------------------------------
/sheetview.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 - 2025 The excelize Authors. All rights reserved. Use of
2 | // this source code is governed by a BSD-style license that can be found in
3 | // the LICENSE file.
4 | //
5 | // Package excelize providing a set of functions that allow you to write to and
6 | // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
7 | // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
8 | // Supports complex components by high compatibility, and provided streaming
9 | // API for generating or reading data from a worksheet with huge amounts of
10 | // data. This library needs Go version 1.23 or later.
11 |
12 | package excelize
13 |
14 | // getSheetView returns the SheetView object
15 | func (f *File) getSheetView(sheet string, viewIndex int) (*xlsxSheetView, error) {
16 | ws, err := f.workSheetReader(sheet)
17 | if err != nil {
18 | return nil, err
19 | }
20 | if ws.SheetViews == nil {
21 | ws.SheetViews = &xlsxSheetViews{
22 | SheetView: []xlsxSheetView{{WorkbookViewID: 0}},
23 | }
24 | }
25 | if viewIndex < 0 {
26 | if viewIndex < -len(ws.SheetViews.SheetView) {
27 | return nil, newViewIdxError(viewIndex)
28 | }
29 | viewIndex = len(ws.SheetViews.SheetView) + viewIndex
30 | } else if viewIndex >= len(ws.SheetViews.SheetView) {
31 | return nil, newViewIdxError(viewIndex)
32 | }
33 |
34 | return &(ws.SheetViews.SheetView[viewIndex]), err
35 | }
36 |
37 | // setSheetView set sheet view by given options.
38 | func (view *xlsxSheetView) setSheetView(opts *ViewOptions) {
39 | if opts.DefaultGridColor != nil {
40 | view.DefaultGridColor = opts.DefaultGridColor
41 | }
42 | if opts.RightToLeft != nil {
43 | view.RightToLeft = *opts.RightToLeft
44 | }
45 | if opts.ShowFormulas != nil {
46 | view.ShowFormulas = *opts.ShowFormulas
47 | }
48 | if opts.ShowGridLines != nil {
49 | view.ShowGridLines = opts.ShowGridLines
50 | }
51 | if opts.ShowRowColHeaders != nil {
52 | view.ShowRowColHeaders = opts.ShowRowColHeaders
53 | }
54 | if opts.ShowRuler != nil {
55 | view.ShowRuler = opts.ShowRuler
56 | }
57 | if opts.ShowZeros != nil {
58 | view.ShowZeros = opts.ShowZeros
59 | }
60 | if opts.TopLeftCell != nil {
61 | view.TopLeftCell = *opts.TopLeftCell
62 | }
63 | if opts.View != nil {
64 | if inStrSlice([]string{"normal", "pageLayout", "pageBreakPreview"}, *opts.View, true) != -1 {
65 | view.View = *opts.View
66 | }
67 | }
68 | if opts.ZoomScale != nil && *opts.ZoomScale >= 10 && *opts.ZoomScale <= 400 {
69 | view.ZoomScale = *opts.ZoomScale
70 | }
71 | }
72 |
73 | // SetSheetView sets sheet view options. The viewIndex may be negative and if
74 | // so is counted backward (-1 is the last view).
75 | func (f *File) SetSheetView(sheet string, viewIndex int, opts *ViewOptions) error {
76 | view, err := f.getSheetView(sheet, viewIndex)
77 | if err != nil {
78 | return err
79 | }
80 | if opts == nil {
81 | return err
82 | }
83 | view.setSheetView(opts)
84 | return nil
85 | }
86 |
87 | // GetSheetView gets the value of sheet view options. The viewIndex may be
88 | // negative and if so is counted backward (-1 is the last view).
89 | func (f *File) GetSheetView(sheet string, viewIndex int) (ViewOptions, error) {
90 | opts := ViewOptions{
91 | DefaultGridColor: boolPtr(true),
92 | ShowFormulas: boolPtr(true),
93 | ShowGridLines: boolPtr(true),
94 | ShowRowColHeaders: boolPtr(true),
95 | ShowRuler: boolPtr(true),
96 | ShowZeros: boolPtr(true),
97 | View: stringPtr("normal"),
98 | ZoomScale: float64Ptr(100),
99 | }
100 | view, err := f.getSheetView(sheet, viewIndex)
101 | if err != nil {
102 | return opts, err
103 | }
104 | if view.DefaultGridColor != nil {
105 | opts.DefaultGridColor = view.DefaultGridColor
106 | }
107 | opts.RightToLeft = boolPtr(view.RightToLeft)
108 | opts.ShowFormulas = boolPtr(view.ShowFormulas)
109 | if view.ShowGridLines != nil {
110 | opts.ShowGridLines = view.ShowGridLines
111 | }
112 | if view.ShowRowColHeaders != nil {
113 | opts.ShowRowColHeaders = view.ShowRowColHeaders
114 | }
115 | if view.ShowRuler != nil {
116 | opts.ShowRuler = view.ShowRuler
117 | }
118 | if view.ShowZeros != nil {
119 | opts.ShowZeros = view.ShowZeros
120 | }
121 | opts.TopLeftCell = stringPtr(view.TopLeftCell)
122 | if view.View != "" {
123 | opts.View = stringPtr(view.View)
124 | }
125 | if view.ZoomScale >= 10 && view.ZoomScale <= 400 {
126 | opts.ZoomScale = float64Ptr(view.ZoomScale)
127 | }
128 | return opts, err
129 | }
130 |
--------------------------------------------------------------------------------
/sheetview_test.go:
--------------------------------------------------------------------------------
1 | package excelize
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestSetView(t *testing.T) {
10 | f := NewFile()
11 | assert.NoError(t, f.SetSheetView("Sheet1", -1, nil))
12 | ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
13 | assert.True(t, ok)
14 | ws.(*xlsxWorksheet).SheetViews = nil
15 | expected := ViewOptions{
16 | DefaultGridColor: boolPtr(false),
17 | RightToLeft: boolPtr(false),
18 | ShowFormulas: boolPtr(false),
19 | ShowGridLines: boolPtr(false),
20 | ShowRowColHeaders: boolPtr(false),
21 | ShowRuler: boolPtr(false),
22 | ShowZeros: boolPtr(false),
23 | TopLeftCell: stringPtr("A1"),
24 | View: stringPtr("normal"),
25 | ZoomScale: float64Ptr(120),
26 | }
27 | assert.NoError(t, f.SetSheetView("Sheet1", 0, &expected))
28 | opts, err := f.GetSheetView("Sheet1", 0)
29 | assert.NoError(t, err)
30 | assert.Equal(t, expected, opts)
31 | // Test set sheet view options with invalid view index
32 | assert.EqualError(t, f.SetSheetView("Sheet1", 1, nil), "view index 1 out of range")
33 | assert.EqualError(t, f.SetSheetView("Sheet1", -2, nil), "view index -2 out of range")
34 | // Test set sheet view options on not exists worksheet
35 | assert.EqualError(t, f.SetSheetView("SheetN", 0, nil), "sheet SheetN does not exist")
36 | }
37 |
38 | func TestGetView(t *testing.T) {
39 | f := NewFile()
40 | _, err := f.getSheetView("SheetN", 0)
41 | assert.EqualError(t, err, "sheet SheetN does not exist")
42 | // Test get sheet view options with invalid view index
43 | _, err = f.GetSheetView("Sheet1", 1)
44 | assert.EqualError(t, err, "view index 1 out of range")
45 | _, err = f.GetSheetView("Sheet1", -2)
46 | assert.EqualError(t, err, "view index -2 out of range")
47 | // Test get sheet view options on not exists worksheet
48 | _, err = f.GetSheetView("SheetN", 0)
49 | assert.EqualError(t, err, "sheet SheetN does not exist")
50 | }
51 |
--------------------------------------------------------------------------------
/sparkline_test.go:
--------------------------------------------------------------------------------
1 | package excelize
2 |
3 | import (
4 | "fmt"
5 | "path/filepath"
6 | "testing"
7 |
8 | "github.com/stretchr/testify/assert"
9 | )
10 |
11 | func TestAddSparkline(t *testing.T) {
12 | f, err := prepareSparklineDataset()
13 | assert.NoError(t, err)
14 |
15 | // Set the columns widths to make the output clearer
16 | style, err := f.NewStyle(&Style{Font: &Font{Bold: true}})
17 | assert.NoError(t, err)
18 | assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "B1", style))
19 | viewOpts, err := f.GetSheetView("Sheet1", 0)
20 | assert.NoError(t, err)
21 | viewOpts.ZoomScale = float64Ptr(150)
22 | assert.NoError(t, f.SetSheetView("Sheet1", 0, &viewOpts))
23 |
24 | assert.NoError(t, f.SetColWidth("Sheet1", "A", "A", 14))
25 | assert.NoError(t, f.SetColWidth("Sheet1", "B", "B", 50))
26 | // Headings
27 | assert.NoError(t, f.SetCellValue("Sheet1", "A1", "Sparkline"))
28 | assert.NoError(t, f.SetCellValue("Sheet1", "B1", "Description"))
29 |
30 | assert.NoError(t, f.SetCellValue("Sheet1", "B2", `A default "line" sparkline.`))
31 | assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{
32 | Location: []string{"A2"},
33 | Range: []string{"Sheet3!A1:J1"},
34 | }))
35 |
36 | assert.NoError(t, f.SetCellValue("Sheet1", "B3", `A default "column" sparkline.`))
37 | assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{
38 | Location: []string{"A3"},
39 | Range: []string{"Sheet3!A2:J2"},
40 | Type: "column",
41 | }))
42 |
43 | assert.NoError(t, f.SetCellValue("Sheet1", "B4", `A default "win/loss" sparkline.`))
44 | assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{
45 | Location: []string{"A4"},
46 | Range: []string{"Sheet3!A3:J3"},
47 | Type: "win_loss",
48 | }))
49 |
50 | assert.NoError(t, f.SetCellValue("Sheet1", "B6", "Line with markers."))
51 | assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{
52 | Location: []string{"A6"},
53 | Range: []string{"Sheet3!A1:J1"},
54 | Markers: true,
55 | }))
56 |
57 | assert.NoError(t, f.SetCellValue("Sheet1", "B7", "Line with high and low points."))
58 | assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{
59 | Location: []string{"A7"},
60 | Range: []string{"Sheet3!A1:J1"},
61 | High: true,
62 | Low: true,
63 | }))
64 |
65 | assert.NoError(t, f.SetCellValue("Sheet1", "B8", "Line with first and last point markers."))
66 | assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{
67 | Location: []string{"A8"},
68 | Range: []string{"Sheet3!A1:J1"},
69 | First: true,
70 | Last: true,
71 | }))
72 |
73 | assert.NoError(t, f.SetCellValue("Sheet1", "B9", "Line with negative point markers."))
74 | assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{
75 | Location: []string{"A9"},
76 | Range: []string{"Sheet3!A1:J1"},
77 | Negative: true,
78 | }))
79 |
80 | assert.NoError(t, f.SetCellValue("Sheet1", "B10", "Line with axis."))
81 | assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{
82 | Location: []string{"A10"},
83 | Range: []string{"Sheet3!A1:J1"},
84 | Axis: true,
85 | }))
86 |
87 | assert.NoError(t, f.SetCellValue("Sheet1", "B12", "Column with default style (1)."))
88 | assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{
89 | Location: []string{"A12"},
90 | Range: []string{"Sheet3!A2:J2"},
91 | Type: "column",
92 | }))
93 |
94 | assert.NoError(t, f.SetCellValue("Sheet1", "B13", "Column with style 2."))
95 | assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{
96 | Location: []string{"A13"},
97 | Range: []string{"Sheet3!A2:J2"},
98 | Type: "column",
99 | Style: 2,
100 | }))
101 |
102 | assert.NoError(t, f.SetCellValue("Sheet1", "B14", "Column with style 3."))
103 | assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{
104 | Location: []string{"A14"},
105 | Range: []string{"Sheet3!A2:J2"},
106 | Type: "column",
107 | Style: 3,
108 | }))
109 |
110 | assert.NoError(t, f.SetCellValue("Sheet1", "B15", "Column with style 4."))
111 | assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{
112 | Location: []string{"A15"},
113 | Range: []string{"Sheet3!A2:J2"},
114 | Type: "column",
115 | Style: 4,
116 | }))
117 |
118 | assert.NoError(t, f.SetCellValue("Sheet1", "B16", "Column with style 5."))
119 | assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{
120 | Location: []string{"A16"},
121 | Range: []string{"Sheet3!A2:J2"},
122 | Type: "column",
123 | Style: 5,
124 | }))
125 |
126 | assert.NoError(t, f.SetCellValue("Sheet1", "B17", "Column with style 6."))
127 | assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{
128 | Location: []string{"A17"},
129 | Range: []string{"Sheet3!A2:J2"},
130 | Type: "column",
131 | Style: 6,
132 | }))
133 |
134 | assert.NoError(t, f.SetCellValue("Sheet1", "B18", "Column with a user defined color."))
135 | assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{
136 | Location: []string{"A18"},
137 | Range: []string{"Sheet3!A2:J2"},
138 | Type: "column",
139 | SeriesColor: "E965E0",
140 | }))
141 |
142 | assert.NoError(t, f.SetCellValue("Sheet1", "B20", "A win/loss sparkline."))
143 | assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{
144 | Location: []string{"A20"},
145 | Range: []string{"Sheet3!A3:J3"},
146 | Type: "win_loss",
147 | }))
148 |
149 | assert.NoError(t, f.SetCellValue("Sheet1", "B21", "A win/loss sparkline with negative points highlighted."))
150 | assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{
151 | Location: []string{"A21"},
152 | Range: []string{"Sheet3!A3:J3"},
153 | Type: "win_loss",
154 | Negative: true,
155 | }))
156 |
157 | assert.NoError(t, f.SetCellValue("Sheet1", "B23", "A left to right column (the default)."))
158 | assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{
159 | Location: []string{"A23"},
160 | Range: []string{"Sheet3!A4:J4"},
161 | Type: "column",
162 | Style: 20,
163 | }))
164 |
165 | assert.NoError(t, f.SetCellValue("Sheet1", "B24", "A right to left column."))
166 | assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{
167 | Location: []string{"A24"},
168 | Range: []string{"Sheet3!A4:J4"},
169 | Type: "column",
170 | Style: 20,
171 | Reverse: true,
172 | }))
173 |
174 | assert.NoError(t, f.SetCellValue("Sheet1", "B25", "Sparkline and text in one cell."))
175 | assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{
176 | Location: []string{"A25"},
177 | Range: []string{"Sheet3!A4:J4"},
178 | Type: "column",
179 | Style: 20,
180 | }))
181 | assert.NoError(t, f.SetCellValue("Sheet1", "A25", "Growth"))
182 |
183 | assert.NoError(t, f.SetCellValue("Sheet1", "B27", "A grouped sparkline. Changes are applied to all three."))
184 | assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{
185 | Location: []string{"A27", "A28", "A29"},
186 | Range: []string{"Sheet3!A5:J5", "Sheet3!A6:J6", "Sheet3!A7:J7"},
187 | Markers: true,
188 | }))
189 |
190 | // Sheet2 sections
191 | assert.NoError(t, f.AddSparkline("Sheet2", &SparklineOptions{
192 | Location: []string{"F3"},
193 | Range: []string{"Sheet2!A3:E3"},
194 | Type: "win_loss",
195 | Negative: true,
196 | }))
197 |
198 | assert.NoError(t, f.AddSparkline("Sheet2", &SparklineOptions{
199 | Location: []string{"F1"},
200 | Range: []string{"Sheet2!A1:E1"},
201 | Markers: true,
202 | }))
203 |
204 | assert.NoError(t, f.AddSparkline("Sheet2", &SparklineOptions{
205 | Location: []string{"F2"},
206 | Range: []string{"Sheet2!A2:E2"},
207 | Type: "column",
208 | Style: 12,
209 | }))
210 |
211 | assert.NoError(t, f.AddSparkline("Sheet2", &SparklineOptions{
212 | Location: []string{"F3"},
213 | Range: []string{"Sheet2!A3:E3"},
214 | Type: "win_loss",
215 | Negative: true,
216 | }))
217 |
218 | // Save spreadsheet by the given path
219 | assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddSparkline.xlsx")))
220 |
221 | // Test error exceptions
222 | assert.EqualError(t, f.AddSparkline("SheetN", &SparklineOptions{
223 | Location: []string{"F3"},
224 | Range: []string{"Sheet2!A3:E3"},
225 | }), "sheet SheetN does not exist")
226 |
227 | assert.Equal(t, ErrParameterRequired, f.AddSparkline("Sheet1", nil))
228 |
229 | // Test add sparkline with invalid sheet name
230 | assert.Equal(t, ErrSheetNameInvalid, f.AddSparkline("Sheet:1", &SparklineOptions{
231 | Location: []string{"F3"},
232 | Range: []string{"Sheet2!A3:E3"},
233 | Type: "win_loss",
234 | Negative: true,
235 | }))
236 |
237 | assert.Equal(t, ErrSparklineLocation, f.AddSparkline("Sheet1", &SparklineOptions{
238 | Range: []string{"Sheet2!A3:E3"},
239 | }))
240 |
241 | assert.Equal(t, ErrSparklineRange, f.AddSparkline("Sheet1", &SparklineOptions{
242 | Location: []string{"F3"},
243 | }))
244 |
245 | assert.Equal(t, ErrSparkline, f.AddSparkline("Sheet1", &SparklineOptions{
246 | Location: []string{"F2", "F3"},
247 | Range: []string{"Sheet2!A3:E3"},
248 | }))
249 |
250 | assert.Equal(t, ErrSparklineType, f.AddSparkline("Sheet1", &SparklineOptions{
251 | Location: []string{"F3"},
252 | Range: []string{"Sheet2!A3:E3"},
253 | Type: "unknown_type",
254 | }))
255 |
256 | assert.Equal(t, ErrSparklineStyle, f.AddSparkline("Sheet1", &SparklineOptions{
257 | Location: []string{"F3"},
258 | Range: []string{"Sheet2!A3:E3"},
259 | Style: -1,
260 | }))
261 |
262 | assert.Equal(t, ErrSparklineStyle, f.AddSparkline("Sheet1", &SparklineOptions{
263 | Location: []string{"F3"},
264 | Range: []string{"Sheet2!A3:E3"},
265 | Style: -1,
266 | }))
267 | // Test creating a conditional format with existing extension lists
268 | ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
269 | assert.True(t, ok)
270 | ws.(*xlsxWorksheet).ExtLst = &xlsxExtLst{Ext: fmt.Sprintf(``, ExtURISlicerListX14, ExtURISparklineGroups)}
271 | assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{
272 | Location: []string{"A3"},
273 | Range: []string{"Sheet3!A2:J2"},
274 | Type: "column",
275 | }))
276 | // Test creating a conditional format with invalid extension list characters
277 | ws.(*xlsxWorksheet).ExtLst.Ext = fmt.Sprintf(``, ExtURISparklineGroups)
278 | assert.EqualError(t, f.AddSparkline("Sheet1", &SparklineOptions{
279 | Location: []string{"A2"},
280 | Range: []string{"Sheet3!A1:J1"},
281 | }), "XML syntax error on line 1: element closed by ")
282 | }
283 |
284 | func TestAppendSparkline(t *testing.T) {
285 | // Test unsupported charset.
286 | f := NewFile()
287 | ws, err := f.workSheetReader("Sheet1")
288 | assert.NoError(t, err)
289 | ws.ExtLst = &xlsxExtLst{Ext: string(MacintoshCyrillicCharset)}
290 | assert.EqualError(t, f.appendSparkline(ws, &xlsxX14SparklineGroup{}, &xlsxX14SparklineGroups{}), "XML syntax error on line 1: invalid UTF-8")
291 | }
292 |
293 | func prepareSparklineDataset() (*File, error) {
294 | f := NewFile()
295 | sheet2 := [][]int{
296 | {-2, 2, 3, -1, 0},
297 | {30, 20, 33, 20, 15},
298 | {1, -1, -1, 1, -1},
299 | }
300 | sheet3 := [][]int{
301 | {-2, 2, 3, -1, 0, -2, 3, 2, 1, 0},
302 | {30, 20, 33, 20, 15, 5, 5, 15, 10, 15},
303 | {1, 1, -1, -1, 1, -1, 1, 1, 1, -1},
304 | {5, 6, 7, 10, 15, 20, 30, 50, 70, 100},
305 | {-2, 2, 3, -1, 0, -2, 3, 2, 1, 0},
306 | {3, -1, 0, -2, 3, 2, 1, 0, 2, 1},
307 | {0, -2, 3, 2, 1, 0, 1, 2, 3, 1},
308 | }
309 | if _, err := f.NewSheet("Sheet2"); err != nil {
310 | return f, err
311 | }
312 | if _, err := f.NewSheet("Sheet3"); err != nil {
313 | return f, err
314 | }
315 | for row, data := range sheet2 {
316 | if err := f.SetSheetRow("Sheet2", fmt.Sprintf("A%d", row+1), &data); err != nil {
317 | fmt.Println(err)
318 | }
319 | }
320 | for row, data := range sheet3 {
321 | if err := f.SetSheetRow("Sheet3", fmt.Sprintf("A%d", row+1), &data); err != nil {
322 | fmt.Println(err)
323 | }
324 | }
325 | return f, nil
326 | }
327 |
--------------------------------------------------------------------------------
/table_test.go:
--------------------------------------------------------------------------------
1 | package excelize
2 |
3 | import (
4 | "fmt"
5 | "path/filepath"
6 | "strings"
7 | "testing"
8 |
9 | "github.com/stretchr/testify/assert"
10 | )
11 |
12 | func TestAddTable(t *testing.T) {
13 | f, err := prepareTestBook1()
14 | assert.NoError(t, err)
15 | assert.NoError(t, f.AddTable("Sheet1", &Table{Range: "B26:A21"}))
16 | assert.NoError(t, f.AddTable("Sheet2", &Table{
17 | Range: "A2:B5",
18 | Name: "table",
19 | StyleName: "TableStyleMedium2",
20 | ShowColumnStripes: true,
21 | ShowFirstColumn: true,
22 | ShowLastColumn: true,
23 | ShowRowStripes: boolPtr(true),
24 | }))
25 | assert.NoError(t, f.AddTable("Sheet2", &Table{
26 | Range: "D1:D11",
27 | ShowHeaderRow: boolPtr(false),
28 | }))
29 | assert.NoError(t, f.AddTable("Sheet2", &Table{Range: "F1:F1", StyleName: "TableStyleMedium8"}))
30 | // Test get tables in worksheet
31 | tables, err := f.GetTables("Sheet2")
32 | assert.Len(t, tables, 3)
33 | assert.NoError(t, err)
34 |
35 | // Test add table with already exist table name
36 | assert.Equal(t, f.AddTable("Sheet2", &Table{Name: "Table1"}), ErrExistsTableName)
37 | // Test add table with invalid table options
38 | assert.Equal(t, f.AddTable("Sheet1", nil), ErrParameterInvalid)
39 | // Test add table in not exist worksheet
40 | assert.EqualError(t, f.AddTable("SheetN", &Table{Range: "B26:A21"}), "sheet SheetN does not exist")
41 | // Test add table with illegal cell reference
42 | assert.Equal(t, f.AddTable("Sheet1", &Table{Range: "A:B1"}), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")))
43 | assert.Equal(t, f.AddTable("Sheet1", &Table{Range: "A1:B"}), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")))
44 |
45 | assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddTable.xlsx")))
46 |
47 | // Test add table with invalid sheet name
48 | assert.Equal(t, ErrSheetNameInvalid, f.AddTable("Sheet:1", &Table{Range: "B26:A21"}))
49 | // Test addTable with illegal cell reference
50 | f = NewFile()
51 | assert.Equal(t, newCoordinatesToCellNameError(0, 0), f.addTable("sheet1", "", 0, 0, 0, 0, 0, nil))
52 | assert.Equal(t, newCoordinatesToCellNameError(0, 0), f.addTable("sheet1", "", 1, 1, 0, 0, 0, nil))
53 | // Test set defined name and add table with invalid name
54 | for _, cases := range []struct {
55 | name string
56 | err error
57 | }{
58 | {name: "1Table", err: newInvalidNameError("1Table")},
59 | {name: "-Table", err: newInvalidNameError("-Table")},
60 | {name: "'Table", err: newInvalidNameError("'Table")},
61 | {name: "Table 1", err: newInvalidNameError("Table 1")},
62 | {name: "A&B", err: newInvalidNameError("A&B")},
63 | {name: "_1Table'", err: newInvalidNameError("_1Table'")},
64 | {name: "\u0f5f\u0fb3\u0f0b\u0f21", err: newInvalidNameError("\u0f5f\u0fb3\u0f0b\u0f21")},
65 | {name: strings.Repeat("c", MaxFieldLength+1), err: ErrNameLength},
66 | } {
67 | assert.Equal(t, cases.err, f.AddTable("Sheet1", &Table{
68 | Range: "A1:B2",
69 | Name: cases.name,
70 | }))
71 | assert.Equal(t, cases.err, f.SetDefinedName(&DefinedName{
72 | Name: cases.name, RefersTo: "Sheet1!$A$2:$D$5",
73 | }))
74 | }
75 | // Test check duplicate table name with unsupported charset table parts
76 | f = NewFile()
77 | f.Pkg.Store("xl/tables/table1.xml", MacintoshCyrillicCharset)
78 | assert.NoError(t, f.AddTable("Sheet1", &Table{Range: "A1:B2"}))
79 | assert.NoError(t, f.Close())
80 | f = NewFile()
81 | // Test add table with workbook with single cells parts
82 | f.Pkg.Store("xl/tables/tableSingleCells1.xml", []byte(""))
83 | assert.NoError(t, f.AddTable("Sheet1", &Table{Range: "A1:B2"}))
84 | // Test add table with workbook with unsupported charset single cells parts
85 | f.Pkg.Store("xl/tables/tableSingleCells1.xml", MacintoshCyrillicCharset)
86 | assert.NoError(t, f.AddTable("Sheet1", &Table{Range: "A1:B2"}))
87 | assert.NoError(t, f.Close())
88 | }
89 |
90 | func TestGetTables(t *testing.T) {
91 | f := NewFile()
92 | // Test get tables in none table worksheet
93 | tables, err := f.GetTables("Sheet1")
94 | assert.Len(t, tables, 0)
95 | assert.NoError(t, err)
96 | // Test get tables in not exist worksheet
97 | _, err = f.GetTables("SheetN")
98 | assert.EqualError(t, err, "sheet SheetN does not exist")
99 | // Test adjust table with unsupported charset
100 | assert.NoError(t, f.AddTable("Sheet1", &Table{Range: "B26:A21"}))
101 | f.Pkg.Store("xl/tables/table1.xml", MacintoshCyrillicCharset)
102 | _, err = f.GetTables("Sheet1")
103 | assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
104 | // Test adjust table with no exist table parts
105 | f.Pkg.Delete("xl/tables/table1.xml")
106 | tables, err = f.GetTables("Sheet1")
107 | assert.Len(t, tables, 0)
108 | assert.NoError(t, err)
109 | }
110 |
111 | func TestDeleteTable(t *testing.T) {
112 | f := NewFile()
113 | assert.NoError(t, f.AddTable("Sheet1", &Table{Range: "A1:B4", Name: "Table1"}))
114 | assert.NoError(t, f.AddTable("Sheet1", &Table{Range: "B26:A21", Name: "Table2"}))
115 | assert.NoError(t, f.DeleteTable("Table2"))
116 | assert.NoError(t, f.DeleteTable("Table1"))
117 | // Test delete table with invalid table name
118 | assert.Equal(t, newInvalidNameError("Table 1"), f.DeleteTable("Table 1"))
119 | // Test delete table with no exist table name
120 | assert.Equal(t, newNoExistTableError("Table"), f.DeleteTable("Table"))
121 | // Test delete table with unsupported charset
122 | f.Sheet.Delete("xl/worksheets/sheet1.xml")
123 | f.Pkg.Store("xl/worksheets/sheet1.xml", MacintoshCyrillicCharset)
124 | assert.EqualError(t, f.DeleteTable("Table1"), "XML syntax error on line 1: invalid UTF-8")
125 | // Test delete table without deleting table header
126 | f = NewFile()
127 | assert.NoError(t, f.SetCellValue("Sheet1", "A1", "Date"))
128 | assert.NoError(t, f.SetCellValue("Sheet1", "B1", "Values"))
129 | assert.NoError(t, f.UpdateLinkedValue())
130 | assert.NoError(t, f.AddTable("Sheet1", &Table{Range: "A1:B2", Name: "Table1"}))
131 | assert.NoError(t, f.DeleteTable("Table1"))
132 | val, err := f.GetCellValue("Sheet1", "A1")
133 | assert.NoError(t, err)
134 | assert.Equal(t, "Date", val)
135 | val, err = f.GetCellValue("Sheet1", "B1")
136 | assert.NoError(t, err)
137 | assert.Equal(t, "Values", val)
138 | }
139 |
140 | func TestSetTableColumns(t *testing.T) {
141 | f := NewFile()
142 | assert.Equal(t, newCoordinatesToCellNameError(1, 0), f.setTableColumns("Sheet1", true, 1, 0, 1, nil))
143 | }
144 |
145 | func TestAutoFilter(t *testing.T) {
146 | outFile := filepath.Join("test", "TestAutoFilter%d.xlsx")
147 | f, err := prepareTestBook1()
148 | assert.NoError(t, err)
149 | for i, opts := range [][]AutoFilterOptions{
150 | {},
151 | {{Column: "B", Expression: ""}},
152 | {{Column: "B", Expression: "x != blanks"}},
153 | {{Column: "B", Expression: "x == blanks"}},
154 | {{Column: "B", Expression: "x != nonblanks"}},
155 | {{Column: "B", Expression: "x == nonblanks"}},
156 | {{Column: "B", Expression: "x <= 1 and x >= 2"}},
157 | {{Column: "B", Expression: "x == 1 or x == 2"}},
158 | {{Column: "B", Expression: "x == 1 or x == 2*"}},
159 | } {
160 | t.Run(fmt.Sprintf("Expression%d", i+1), func(t *testing.T) {
161 | assert.NoError(t, f.AutoFilter("Sheet1", "D4:B1", opts))
162 | assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, i+1)))
163 | })
164 | }
165 |
166 | // Test add auto filter with invalid sheet name
167 | assert.Equal(t, ErrSheetNameInvalid, f.AutoFilter("Sheet:1", "A1:B1", nil))
168 | // Test add auto filter with illegal cell reference
169 | assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), f.AutoFilter("Sheet1", "A:B1", nil))
170 | assert.Equal(t, newCellNameToCoordinatesError("B", newInvalidCellNameError("B")), f.AutoFilter("Sheet1", "A1:B", nil))
171 | // Test add auto filter with unsupported charset workbook
172 | f.WorkBook = nil
173 | f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
174 | assert.EqualError(t, f.AutoFilter("Sheet1", "D4:B1", nil), "XML syntax error on line 1: invalid UTF-8")
175 | // Test add auto filter with empty local sheet ID
176 | f = NewFile()
177 | f.WorkBook = &xlsxWorkbook{DefinedNames: &xlsxDefinedNames{DefinedName: []xlsxDefinedName{{Name: builtInDefinedNames[3], Hidden: true}}}}
178 | assert.NoError(t, f.AutoFilter("Sheet1", "A1:B1", nil))
179 | }
180 |
181 | func TestAutoFilterError(t *testing.T) {
182 | outFile := filepath.Join("test", "TestAutoFilterError%d.xlsx")
183 | f, err := prepareTestBook1()
184 | assert.NoError(t, err)
185 | for i, opts := range [][]AutoFilterOptions{
186 | {{Column: "B", Expression: "x <= 1 and x >= blanks"}},
187 | {{Column: "B", Expression: "x -- y or x == *2*"}},
188 | {{Column: "B", Expression: "x != y or x ? *2"}},
189 | {{Column: "B", Expression: "x -- y o r x == *2"}},
190 | {{Column: "B", Expression: "x -- y"}},
191 | {{Column: "A", Expression: "x -- y"}},
192 | } {
193 | t.Run(fmt.Sprintf("Expression%d", i+1), func(t *testing.T) {
194 | if assert.Error(t, f.AutoFilter("Sheet2", "D4:B1", opts)) {
195 | assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, i+1)))
196 | }
197 | })
198 | }
199 |
200 | assert.Equal(t, ErrSheetNotExist{"SheetN"}, f.autoFilter("SheetN", "A1", 1, 1, []AutoFilterOptions{{
201 | Column: "A",
202 | Expression: "",
203 | }}))
204 | assert.Equal(t, newInvalidColumnNameError("-"), f.autoFilter("Sheet1", "A1", 1, 1, []AutoFilterOptions{{
205 | Column: "-",
206 | Expression: "-",
207 | }}))
208 | assert.Equal(t, newInvalidAutoFilterColumnError("A"), f.autoFilter("Sheet1", "A1", 1, 100, []AutoFilterOptions{{
209 | Column: "A",
210 | Expression: "-",
211 | }}))
212 | assert.Equal(t, newInvalidAutoFilterExpError("-"), f.autoFilter("Sheet1", "A1", 1, 1, []AutoFilterOptions{{
213 | Column: "A",
214 | Expression: "-",
215 | }}))
216 | }
217 |
218 | func TestParseFilterTokens(t *testing.T) {
219 | f := NewFile()
220 | // Test with unknown operator
221 | _, _, err := f.parseFilterTokens("", []string{"", "!"})
222 | assert.EqualError(t, err, "unknown operator: !")
223 | // Test invalid operator in context
224 | _, _, err = f.parseFilterTokens("", []string{"", "<", "x != blanks"})
225 | assert.Equal(t, newInvalidAutoFilterOperatorError("<", ""), err)
226 | }
227 |
--------------------------------------------------------------------------------
/test/BadWorkbook.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qax-os/excelize/c5934ea0e7d0f5d049e131eb2f0d6f35563172c4/test/BadWorkbook.xlsx
--------------------------------------------------------------------------------
/test/Book1.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qax-os/excelize/c5934ea0e7d0f5d049e131eb2f0d6f35563172c4/test/Book1.xlsx
--------------------------------------------------------------------------------
/test/CalcChain.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qax-os/excelize/c5934ea0e7d0f5d049e131eb2f0d6f35563172c4/test/CalcChain.xlsx
--------------------------------------------------------------------------------
/test/MergeCell.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qax-os/excelize/c5934ea0e7d0f5d049e131eb2f0d6f35563172c4/test/MergeCell.xlsx
--------------------------------------------------------------------------------
/test/OverflowNumericCell.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qax-os/excelize/c5934ea0e7d0f5d049e131eb2f0d6f35563172c4/test/OverflowNumericCell.xlsx
--------------------------------------------------------------------------------
/test/SharedStrings.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qax-os/excelize/c5934ea0e7d0f5d049e131eb2f0d6f35563172c4/test/SharedStrings.xlsx
--------------------------------------------------------------------------------
/test/encryptAES.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qax-os/excelize/c5934ea0e7d0f5d049e131eb2f0d6f35563172c4/test/encryptAES.xlsx
--------------------------------------------------------------------------------
/test/encryptSHA1.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qax-os/excelize/c5934ea0e7d0f5d049e131eb2f0d6f35563172c4/test/encryptSHA1.xlsx
--------------------------------------------------------------------------------
/test/images/background.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qax-os/excelize/c5934ea0e7d0f5d049e131eb2f0d6f35563172c4/test/images/background.jpg
--------------------------------------------------------------------------------
/test/images/chart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qax-os/excelize/c5934ea0e7d0f5d049e131eb2f0d6f35563172c4/test/images/chart.png
--------------------------------------------------------------------------------
/test/images/excel.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qax-os/excelize/c5934ea0e7d0f5d049e131eb2f0d6f35563172c4/test/images/excel.bmp
--------------------------------------------------------------------------------
/test/images/excel.emf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qax-os/excelize/c5934ea0e7d0f5d049e131eb2f0d6f35563172c4/test/images/excel.emf
--------------------------------------------------------------------------------
/test/images/excel.emz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qax-os/excelize/c5934ea0e7d0f5d049e131eb2f0d6f35563172c4/test/images/excel.emz
--------------------------------------------------------------------------------
/test/images/excel.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qax-os/excelize/c5934ea0e7d0f5d049e131eb2f0d6f35563172c4/test/images/excel.gif
--------------------------------------------------------------------------------
/test/images/excel.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qax-os/excelize/c5934ea0e7d0f5d049e131eb2f0d6f35563172c4/test/images/excel.jpg
--------------------------------------------------------------------------------
/test/images/excel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qax-os/excelize/c5934ea0e7d0f5d049e131eb2f0d6f35563172c4/test/images/excel.png
--------------------------------------------------------------------------------
/test/images/excel.tif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qax-os/excelize/c5934ea0e7d0f5d049e131eb2f0d6f35563172c4/test/images/excel.tif
--------------------------------------------------------------------------------
/test/images/excel.wmf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qax-os/excelize/c5934ea0e7d0f5d049e131eb2f0d6f35563172c4/test/images/excel.wmf
--------------------------------------------------------------------------------
/test/images/excel.wmz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qax-os/excelize/c5934ea0e7d0f5d049e131eb2f0d6f35563172c4/test/images/excel.wmz
--------------------------------------------------------------------------------
/test/vbaProject.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qax-os/excelize/c5934ea0e7d0f5d049e131eb2f0d6f35563172c4/test/vbaProject.bin
--------------------------------------------------------------------------------
/workbook_test.go:
--------------------------------------------------------------------------------
1 | package excelize
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestWorkbookProps(t *testing.T) {
10 | f := NewFile()
11 | assert.NoError(t, f.SetWorkbookProps(nil))
12 | wb, err := f.workbookReader()
13 | assert.NoError(t, err)
14 | wb.WorkbookPr = nil
15 | expected := WorkbookPropsOptions{
16 | Date1904: boolPtr(true),
17 | FilterPrivacy: boolPtr(true),
18 | CodeName: stringPtr("code"),
19 | }
20 | assert.NoError(t, f.SetWorkbookProps(&expected))
21 | opts, err := f.GetWorkbookProps()
22 | assert.NoError(t, err)
23 | assert.Equal(t, expected, opts)
24 | wb.WorkbookPr = nil
25 | opts, err = f.GetWorkbookProps()
26 | assert.NoError(t, err)
27 | assert.Equal(t, WorkbookPropsOptions{}, opts)
28 | // Test set workbook properties with unsupported charset workbook
29 | f.WorkBook = nil
30 | f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
31 | assert.EqualError(t, f.SetWorkbookProps(&expected), "XML syntax error on line 1: invalid UTF-8")
32 | // Test get workbook properties with unsupported charset workbook
33 | f.WorkBook = nil
34 | f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
35 | _, err = f.GetWorkbookProps()
36 | assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
37 | }
38 |
39 | func TestCalcProps(t *testing.T) {
40 | f := NewFile()
41 | assert.NoError(t, f.SetCalcProps(nil))
42 | wb, err := f.workbookReader()
43 | assert.NoError(t, err)
44 | wb.CalcPr = nil
45 | expected := CalcPropsOptions{
46 | FullCalcOnLoad: boolPtr(true),
47 | CalcID: uintPtr(122211),
48 | ConcurrentManualCount: uintPtr(5),
49 | IterateCount: uintPtr(10),
50 | ConcurrentCalc: boolPtr(true),
51 | }
52 | assert.NoError(t, f.SetCalcProps(&expected))
53 | opts, err := f.GetCalcProps()
54 | assert.NoError(t, err)
55 | assert.Equal(t, expected, opts)
56 | wb.CalcPr = nil
57 | opts, err = f.GetCalcProps()
58 | assert.NoError(t, err)
59 | assert.Equal(t, CalcPropsOptions{}, opts)
60 | // Test set calculation properties with unsupported optional value
61 | assert.Equal(t, newInvalidOptionalValue("CalcMode", "AUTO", supportedCalcMode), f.SetCalcProps(&CalcPropsOptions{CalcMode: stringPtr("AUTO")}))
62 | assert.Equal(t, newInvalidOptionalValue("RefMode", "a1", supportedRefMode), f.SetCalcProps(&CalcPropsOptions{RefMode: stringPtr("a1")}))
63 | // Test set calculation properties with unsupported charset workbook
64 | f.WorkBook = nil
65 | f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
66 | assert.EqualError(t, f.SetCalcProps(&expected), "XML syntax error on line 1: invalid UTF-8")
67 | // Test get calculation properties with unsupported charset workbook
68 | f.WorkBook = nil
69 | f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
70 | _, err = f.GetCalcProps()
71 | assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
72 | }
73 |
74 | func TestDeleteWorkbookRels(t *testing.T) {
75 | f := NewFile()
76 | // Test delete pivot table without worksheet relationships
77 | f.Relationships.Delete("xl/_rels/workbook.xml.rels")
78 | f.Pkg.Delete("xl/_rels/workbook.xml.rels")
79 | rID, err := f.deleteWorkbookRels("", "")
80 | assert.Empty(t, rID)
81 | assert.NoError(t, err)
82 | }
83 |
--------------------------------------------------------------------------------
/xmlApp.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 - 2025 The excelize Authors. All rights reserved. Use of
2 | // this source code is governed by a BSD-style license that can be found in
3 | // the LICENSE file.
4 | //
5 | // Package excelize providing a set of functions that allow you to write to and
6 | // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
7 | // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
8 | // Supports complex components by high compatibility, and provided streaming
9 | // API for generating or reading data from a worksheet with huge amounts of
10 | // data. This library needs Go version 1.23 or later.
11 |
12 | package excelize
13 |
14 | import "encoding/xml"
15 |
16 | // AppProperties directly maps the document application properties.
17 | type AppProperties struct {
18 | Application string
19 | ScaleCrop bool
20 | DocSecurity int
21 | Company string
22 | LinksUpToDate bool
23 | HyperlinksChanged bool
24 | AppVersion string
25 | }
26 |
27 | // xlsxProperties specifies to an OOXML document properties such as the
28 | // template used, the number of pages and words, and the application name and
29 | // version.
30 | type xlsxProperties struct {
31 | XMLName xml.Name `xml:"http://schemas.openxmlformats.org/officeDocument/2006/extended-properties Properties"`
32 | Vt string `xml:"xmlns:vt,attr"`
33 | Template string `xml:",omitempty"`
34 | Manager string `xml:",omitempty"`
35 | Company string `xml:",omitempty"`
36 | Pages int `xml:",omitempty"`
37 | Words int `xml:",omitempty"`
38 | Characters int `xml:",omitempty"`
39 | PresentationFormat string `xml:",omitempty"`
40 | Lines int `xml:",omitempty"`
41 | Paragraphs int `xml:",omitempty"`
42 | Slides int `xml:",omitempty"`
43 | Notes int `xml:",omitempty"`
44 | TotalTime int `xml:",omitempty"`
45 | HiddenSlides int `xml:",omitempty"`
46 | MMClips int `xml:",omitempty"`
47 | ScaleCrop bool `xml:",omitempty"`
48 | HeadingPairs *xlsxVectorVariant
49 | TitlesOfParts *xlsxVectorLpstr
50 | LinksUpToDate bool `xml:",omitempty"`
51 | CharactersWithSpaces int `xml:",omitempty"`
52 | SharedDoc bool `xml:",omitempty"`
53 | HyperlinkBase string `xml:",omitempty"`
54 | HLinks *xlsxVectorVariant
55 | HyperlinksChanged bool `xml:",omitempty"`
56 | DigSig *xlsxDigSig
57 | Application string `xml:",omitempty"`
58 | AppVersion string `xml:",omitempty"`
59 | DocSecurity int `xml:",omitempty"`
60 | }
61 |
62 | // xlsxVectorVariant specifies the set of hyperlinks that were in this
63 | // document when last saved.
64 | type xlsxVectorVariant struct {
65 | Content string `xml:",innerxml"`
66 | }
67 |
68 | type xlsxVectorLpstr struct {
69 | Content string `xml:",innerxml"`
70 | }
71 |
72 | // xlsxDigSig contains the signature of a digitally signed document.
73 | type xlsxDigSig struct {
74 | Content string `xml:",innerxml"`
75 | }
76 |
--------------------------------------------------------------------------------
/xmlCalcChain.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 - 2025 The excelize Authors. All rights reserved. Use of
2 | // this source code is governed by a BSD-style license that can be found in
3 | // the LICENSE file.
4 | //
5 | // Package excelize providing a set of functions that allow you to write to and
6 | // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
7 | // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
8 | // Supports complex components by high compatibility, and provided streaming
9 | // API for generating or reading data from a worksheet with huge amounts of
10 | // data. This library needs Go version 1.23 or later.
11 |
12 | package excelize
13 |
14 | import "encoding/xml"
15 |
16 | // xlsxCalcChain directly maps the calcChain element. This element represents
17 | // the root of the calculation chain.
18 | type xlsxCalcChain struct {
19 | XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main calcChain"`
20 | C []xlsxCalcChainC `xml:"c"`
21 | }
22 |
23 | // xlsxCalcChainC directly maps the c element.
24 | //
25 | // Attributes | Attributes
26 | // --------------------------+----------------------------------------------------------
27 | // a (Array) | A Boolean flag indicating whether the cell's formula
28 | // | is an array formula. True if this cell's formula is
29 | // | an array formula, false otherwise. If there is a
30 | // | conflict between this attribute and the t attribute
31 | // | of the f element (§18.3.1.40), the t attribute takes
32 | // | precedence. The possible values for this attribute
33 | // | are defined by the W3C XML Schema boolean datatype.
34 | // |
35 | // i (Sheet Id) | A sheet Id of a sheet the cell belongs to. If this is
36 | // | omitted, it is assumed to be the same as the i value
37 | // | of the previous cell.The possible values for this
38 | // | attribute are defined by the W3C XML Schema int datatype.
39 | // |
40 | // l (New Dependency Level) | A Boolean flag indicating that the cell's formula
41 | // | starts a new dependency level. True if the formula
42 | // | starts a new dependency level, false otherwise.
43 | // | Starting a new dependency level means that all
44 | // | concurrent calculations, and child calculations, shall
45 | // | be completed - and the cells have new values - before
46 | // | the calc chain can continue. In other words, this
47 | // | dependency level might depend on levels that came before
48 | // | it, and any later dependency levels might depend on
49 | // | this level; but not later dependency levels can have
50 | // | any calculations started until this dependency level
51 | // | completes.The possible values for this attribute are
52 | // | defined by the W3C XML Schema boolean datatype.
53 | // |
54 | // r (Cell Reference) | An A-1 style reference to a cell.The possible values
55 | // | for this attribute are defined by the ST_CellRef
56 | // | simple type (§18.18.7).
57 | // |
58 | // s (Child Chain) | A Boolean flag indicating whether the cell's formula
59 | // | is on a child chain. True if this cell is part of a
60 | // | child chain, false otherwise. If this is omitted, it
61 | // | is assumed to be the same as the s value of the
62 | // | previous cell .A child chain is a list of calculations
63 | // | that occur which depend on the parent to the chain.
64 | // | There shall not be cross dependencies between child
65 | // | chains. Child chains are not the same as dependency
66 | // | levels - a child chain and its parent are all on the
67 | // | same dependency level. Child chains are series of
68 | // | calculations that can be independently farmed out to
69 | // | other threads or processors.The possible values for
70 | // | this attribute is defined by the W3C XML Schema
71 | // | boolean datatype.
72 | // |
73 | // t (New Thread) | A Boolean flag indicating whether the cell's formula
74 | // | starts a new thread. True if the cell's formula starts
75 | // | a new thread, false otherwise.The possible values for
76 | // | this attribute is defined by the W3C XML Schema
77 | // | boolean datatype.
78 | type xlsxCalcChainC struct {
79 | R string `xml:"r,attr"`
80 | I int `xml:"i,attr,omitempty"`
81 | L bool `xml:"l,attr,omitempty"`
82 | S bool `xml:"s,attr,omitempty"`
83 | T bool `xml:"t,attr,omitempty"`
84 | A bool `xml:"a,attr,omitempty"`
85 | }
86 |
87 | // xlsxVolTypes maps the volatileDependencies part provides a cache of data that
88 | // supports Real Time Data (RTD) and CUBE functions in the workbook.
89 | type xlsxVolTypes struct {
90 | XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main volTypes"`
91 | VolType []xlsxVolType `xml:"volType"`
92 | ExtLst *xlsxExtLst `xml:"extLst"`
93 | }
94 |
95 | // xlsxVolType represents dependency information for a specific type of external
96 | // data server.
97 | type xlsxVolType struct {
98 | Type string `xml:"type,attr"`
99 | Main []xlsxVolMain `xml:"main"`
100 | }
101 |
102 | // xlsxVolMain represents dependency information for all topics within a
103 | // volatile dependency type that share the same first string or function
104 | // argument.
105 | type xlsxVolMain struct {
106 | First string `xml:"first,attr"`
107 | Tp []xlsxVolTopic `xml:"tp"`
108 | }
109 |
110 | // xlsxVolTopic represents dependency information for all topics within a
111 | // volatile dependency type that share the same first string or argument.
112 | type xlsxVolTopic struct {
113 | T string `xml:"t,attr,omitempty"`
114 | V string `xml:"v"`
115 | Stp []string `xml:"stp"`
116 | Tr []xlsxVolTopicRef `xml:"tr"`
117 | }
118 |
119 | // xlsxVolTopicRef represents the reference to a cell that depends on this
120 | // topic. Each topic can have one or more cells dependencies.
121 | type xlsxVolTopicRef struct {
122 | R string `xml:"r,attr"`
123 | S int `xml:"s,attr"`
124 | }
125 |
--------------------------------------------------------------------------------
/xmlChartSheet.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 - 2025 The excelize Authors. All rights reserved. Use of
2 | // this source code is governed by a BSD-style license that can be found in
3 | // the LICENSE file.
4 | //
5 | // struct code generated by github.com/xuri/xgen
6 | //
7 | // Package excelize providing a set of functions that allow you to write to and
8 | // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
9 | // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
10 | // Supports complex components by high compatibility, and provided streaming
11 | // API for generating or reading data from a worksheet with huge amounts of
12 | // data. This library needs Go version 1.23 or later.
13 |
14 | package excelize
15 |
16 | import "encoding/xml"
17 |
18 | // xlsxChartsheet directly maps the chartsheet element of Chartsheet Parts in
19 | // a SpreadsheetML document.
20 | type xlsxChartsheet struct {
21 | XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main chartsheet"`
22 | SheetPr *xlsxChartsheetPr `xml:"sheetPr"`
23 | SheetViews *xlsxChartsheetViews `xml:"sheetViews"`
24 | SheetProtection *xlsxChartsheetProtection `xml:"sheetProtection"`
25 | CustomSheetViews *xlsxCustomChartsheetViews `xml:"customSheetViews"`
26 | PageMargins *xlsxPageMargins `xml:"pageMargins"`
27 | PageSetup *xlsxPageSetUp `xml:"pageSetup"`
28 | HeaderFooter *xlsxHeaderFooter `xml:"headerFooter"`
29 | Drawing *xlsxDrawing `xml:"drawing"`
30 | DrawingHF *xlsxDrawingHF `xml:"drawingHF"`
31 | Picture *xlsxPicture `xml:"picture"`
32 | WebPublishItems *xlsxInnerXML `xml:"webPublishItems"`
33 | ExtLst *xlsxExtLst `xml:"extLst"`
34 | }
35 |
36 | // xlsxChartsheetPr specifies chart sheet properties.
37 | type xlsxChartsheetPr struct {
38 | XMLName xml.Name `xml:"sheetPr"`
39 | PublishedAttr bool `xml:"published,attr,omitempty"`
40 | CodeNameAttr string `xml:"codeName,attr,omitempty"`
41 | TabColor *xlsxColor `xml:"tabColor"`
42 | }
43 |
44 | // xlsxChartsheetViews specifies chart sheet views.
45 | type xlsxChartsheetViews struct {
46 | XMLName xml.Name `xml:"sheetViews"`
47 | SheetView []*xlsxChartsheetView `xml:"sheetView"`
48 | ExtLst []*xlsxExtLst `xml:"extLst"`
49 | }
50 |
51 | // xlsxChartsheetView defines custom view properties for chart sheets.
52 | type xlsxChartsheetView struct {
53 | XMLName xml.Name `xml:"sheetView"`
54 | TabSelectedAttr bool `xml:"tabSelected,attr,omitempty"`
55 | ZoomScaleAttr uint32 `xml:"zoomScale,attr,omitempty"`
56 | WorkbookViewIDAttr uint32 `xml:"workbookViewId,attr"`
57 | ZoomToFitAttr bool `xml:"zoomToFit,attr,omitempty"`
58 | ExtLst []*xlsxExtLst `xml:"extLst"`
59 | }
60 |
61 | // xlsxChartsheetProtection collection expresses the chart sheet protection
62 | // options to enforce when the chart sheet is protected.
63 | type xlsxChartsheetProtection struct {
64 | XMLName xml.Name `xml:"sheetProtection"`
65 | AlgorithmNameAttr string `xml:"algorithmName,attr,omitempty"`
66 | HashValueAttr []byte `xml:"hashValue,attr,omitempty"`
67 | SaltValueAttr []byte `xml:"saltValue,attr,omitempty"`
68 | SpinCountAttr uint32 `xml:"spinCount,attr,omitempty"`
69 | ContentAttr bool `xml:"content,attr,omitempty"`
70 | ObjectsAttr bool `xml:"objects,attr,omitempty"`
71 | }
72 |
73 | // xlsxCustomChartsheetViews collection of custom Chart Sheet View
74 | // information.
75 | type xlsxCustomChartsheetViews struct {
76 | XMLName xml.Name `xml:"customSheetViews"`
77 | CustomSheetView []*xlsxCustomChartsheetView `xml:"customSheetView"`
78 | }
79 |
80 | // xlsxCustomChartsheetView defines custom view properties for chart sheets.
81 | type xlsxCustomChartsheetView struct {
82 | XMLName xml.Name `xml:"customSheetView"`
83 | GUIDAttr string `xml:"guid,attr"`
84 | ScaleAttr uint32 `xml:"scale,attr,omitempty"`
85 | StateAttr string `xml:"state,attr,omitempty"`
86 | ZoomToFitAttr bool `xml:"zoomToFit,attr,omitempty"`
87 | PageMargins []*xlsxPageMargins `xml:"pageMargins"`
88 | PageSetup []*xlsxPageSetUp `xml:"pageSetup"`
89 | HeaderFooter []*xlsxHeaderFooter `xml:"headerFooter"`
90 | }
91 |
--------------------------------------------------------------------------------
/xmlComments.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 - 2025 The excelize Authors. All rights reserved. Use of
2 | // this source code is governed by a BSD-style license that can be found in
3 | // the LICENSE file.
4 | //
5 | // Package excelize providing a set of functions that allow you to write to and
6 | // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
7 | // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
8 | // Supports complex components by high compatibility, and provided streaming
9 | // API for generating or reading data from a worksheet with huge amounts of
10 | // data. This library needs Go version 1.23 or later.
11 |
12 | package excelize
13 |
14 | import "encoding/xml"
15 |
16 | // xlsxComments directly maps the comments element from the namespace
17 | // http://schemas.openxmlformats.org/spreadsheetml/2006/main. A comment is a
18 | // rich text note that is attached to and associated with a cell, separate from
19 | // other cell content. Comment content is stored separate from the cell, and is
20 | // displayed in a drawing object (like a text box) that is separate from, but
21 | // associated with, a cell. Comments are used as reminders, such as noting how a
22 | // complex formula works, or to provide feedback to other users. Comments can
23 | // also be used to explain assumptions made in a formula or to call out
24 | // something special about the cell.
25 | type xlsxComments struct {
26 | XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main comments"`
27 | Authors xlsxAuthor `xml:"authors"`
28 | CommentList xlsxCommentList `xml:"commentList"`
29 | }
30 |
31 | // xlsxAuthor directly maps the author element. This element holds a string
32 | // representing the name of a single author of comments. Every comment shall
33 | // have an author. The maximum length of the author string is an implementation
34 | // detail, but a good guideline is 255 chars.
35 | type xlsxAuthor struct {
36 | Author []string `xml:"author"`
37 | }
38 |
39 | // xlsxCommentList (List of Comments) directly maps the xlsxCommentList element.
40 | // This element is a container that holds a list of comments for the sheet.
41 | type xlsxCommentList struct {
42 | Comment []xlsxComment `xml:"comment"`
43 | }
44 |
45 | // xlsxComment directly maps the comment element. This element represents a
46 | // single user entered comment. Each comment shall have an author and can
47 | // optionally contain richly formatted text.
48 | type xlsxComment struct {
49 | Ref string `xml:"ref,attr"`
50 | AuthorID int `xml:"authorId,attr"`
51 | Text xlsxText `xml:"text"`
52 | }
53 |
54 | // xlsxText directly maps the text element. This element contains rich text
55 | // which represents the text of a comment. The maximum length for this text is a
56 | // spreadsheet application implementation detail. A recommended guideline is
57 | // 32767 chars.
58 | type xlsxText struct {
59 | T *string `xml:"t"`
60 | R []xlsxR `xml:"r"`
61 | RPh *xlsxPhoneticRun `xml:"rPh"`
62 | PhoneticPr *xlsxPhoneticPr `xml:"phoneticPr"`
63 | }
64 |
65 | // xlsxPhoneticRun element represents a run of text which displays a phonetic
66 | // hint for this String Item (si). Phonetic hints are used to give information
67 | // about the pronunciation of an East Asian language. The hints are displayed
68 | // as text within the spreadsheet cells across the top portion of the cell.
69 | type xlsxPhoneticRun struct {
70 | Sb uint32 `xml:"sb,attr"`
71 | Eb uint32 `xml:"eb,attr"`
72 | T string `xml:"t"`
73 | }
74 |
75 | // Comment directly maps the comment information.
76 | type Comment struct {
77 | Author string
78 | AuthorID int
79 | Cell string
80 | Text string
81 | Width uint
82 | Height uint
83 | Paragraph []RichTextRun
84 | }
85 |
--------------------------------------------------------------------------------
/xmlContentTypes.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 - 2025 The excelize Authors. All rights reserved. Use of
2 | // this source code is governed by a BSD-style license that can be found in
3 | // the LICENSE file.
4 | //
5 | // Package excelize providing a set of functions that allow you to write to and
6 | // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
7 | // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
8 | // Supports complex components by high compatibility, and provided streaming
9 | // API for generating or reading data from a worksheet with huge amounts of
10 | // data. This library needs Go version 1.23 or later.
11 |
12 | package excelize
13 |
14 | import (
15 | "encoding/xml"
16 | "sync"
17 | )
18 |
19 | // xlsxTypes directly maps the types' element of content types for relationship
20 | // parts, it takes a Multipurpose Internet Mail Extension (MIME) media type as a
21 | // value.
22 | type xlsxTypes struct {
23 | mu sync.Mutex
24 | XMLName xml.Name `xml:"http://schemas.openxmlformats.org/package/2006/content-types Types"`
25 | Defaults []xlsxDefault `xml:"Default"`
26 | Overrides []xlsxOverride `xml:"Override"`
27 | }
28 |
29 | // xlsxOverride directly maps the override element in the namespace
30 | // http://schemas.openxmlformats.org/package/2006/content-types
31 | type xlsxOverride struct {
32 | PartName string `xml:",attr"`
33 | ContentType string `xml:",attr"`
34 | }
35 |
36 | // xlsxDefault directly maps the default element in the namespace
37 | // http://schemas.openxmlformats.org/package/2006/content-types
38 | type xlsxDefault struct {
39 | Extension string `xml:",attr"`
40 | ContentType string `xml:",attr"`
41 | }
42 |
--------------------------------------------------------------------------------
/xmlCore.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 - 2025 The excelize Authors. All rights reserved. Use of
2 | // this source code is governed by a BSD-style license that can be found in
3 | // the LICENSE file.
4 | //
5 | // Package excelize providing a set of functions that allow you to write to and
6 | // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
7 | // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
8 | // Supports complex components by high compatibility, and provided streaming
9 | // API for generating or reading data from a worksheet with huge amounts of
10 | // data. This library needs Go version 1.23 or later.
11 |
12 | package excelize
13 |
14 | import "encoding/xml"
15 |
16 | // DocProperties directly maps the document core properties.
17 | type DocProperties struct {
18 | Category string
19 | ContentStatus string
20 | Created string
21 | Creator string
22 | Description string
23 | Identifier string
24 | Keywords string
25 | LastModifiedBy string
26 | Modified string
27 | Revision string
28 | Subject string
29 | Title string
30 | Language string
31 | Version string
32 | }
33 |
34 | // decodeDcTerms directly maps the DCMI metadata terms for the coreProperties.
35 | type decodeDcTerms struct {
36 | Text string `xml:",chardata"`
37 | Type string `xml:"http://www.w3.org/2001/XMLSchema-instance type,attr"`
38 | }
39 |
40 | // decodeCoreProperties directly maps the root element for a part of this
41 | // content type shall coreProperties. In order to solve the problem that the
42 | // label structure is changed after serialization and deserialization, two
43 | // different structures are defined. decodeCoreProperties just for
44 | // deserialization.
45 | type decodeCoreProperties struct {
46 | XMLName xml.Name `xml:"http://schemas.openxmlformats.org/package/2006/metadata/core-properties coreProperties"`
47 | Title string `xml:"http://purl.org/dc/elements/1.1/ title,omitempty"`
48 | Subject string `xml:"http://purl.org/dc/elements/1.1/ subject,omitempty"`
49 | Creator string `xml:"http://purl.org/dc/elements/1.1/ creator"`
50 | Keywords string `xml:"keywords,omitempty"`
51 | Description string `xml:"http://purl.org/dc/elements/1.1/ description,omitempty"`
52 | LastModifiedBy string `xml:"lastModifiedBy"`
53 | Language string `xml:"http://purl.org/dc/elements/1.1/ language,omitempty"`
54 | Identifier string `xml:"http://purl.org/dc/elements/1.1/ identifier,omitempty"`
55 | Revision string `xml:"revision,omitempty"`
56 | Created *decodeDcTerms `xml:"http://purl.org/dc/terms/ created"`
57 | Modified *decodeDcTerms `xml:"http://purl.org/dc/terms/ modified"`
58 | ContentStatus string `xml:"contentStatus,omitempty"`
59 | Category string `xml:"category,omitempty"`
60 | Version string `xml:"version,omitempty"`
61 | }
62 |
63 | // xlsxDcTerms directly maps the DCMI metadata terms for the coreProperties.
64 | type xlsxDcTerms struct {
65 | Text string `xml:",chardata"`
66 | Type string `xml:"xsi:type,attr"`
67 | }
68 |
69 | // xlsxCoreProperties directly maps the root element for a part of this
70 | // content type shall coreProperties.
71 | type xlsxCoreProperties struct {
72 | XMLName xml.Name `xml:"http://schemas.openxmlformats.org/package/2006/metadata/core-properties coreProperties"`
73 | Dc string `xml:"xmlns:dc,attr"`
74 | Dcterms string `xml:"xmlns:dcterms,attr"`
75 | Dcmitype string `xml:"xmlns:dcmitype,attr"`
76 | XSI string `xml:"xmlns:xsi,attr"`
77 | Title string `xml:"dc:title,omitempty"`
78 | Subject string `xml:"dc:subject,omitempty"`
79 | Creator string `xml:"dc:creator"`
80 | Keywords string `xml:"keywords,omitempty"`
81 | Description string `xml:"dc:description,omitempty"`
82 | LastModifiedBy string `xml:"lastModifiedBy"`
83 | Language string `xml:"dc:language,omitempty"`
84 | Identifier string `xml:"dc:identifier,omitempty"`
85 | Revision string `xml:"revision,omitempty"`
86 | Created *xlsxDcTerms `xml:"dcterms:created"`
87 | Modified *xlsxDcTerms `xml:"dcterms:modified"`
88 | ContentStatus string `xml:"contentStatus,omitempty"`
89 | Category string `xml:"category,omitempty"`
90 | Version string `xml:"version,omitempty"`
91 | }
92 |
--------------------------------------------------------------------------------
/xmlMetaData.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 - 2025 The excelize Authors. All rights reserved. Use of
2 | // this source code is governed by a BSD-style license that can be found in
3 | // the LICENSE file.
4 | //
5 | // Package excelize providing a set of functions that allow you to write to and
6 | // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
7 | // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
8 | // Supports complex components by high compatibility, and provided streaming
9 | // API for generating or reading data from a worksheet with huge amounts of
10 | // data. This library needs Go version 1.23 or later.
11 |
12 | package excelize
13 |
14 | import "encoding/xml"
15 |
16 | // xlsxMetadata directly maps the metadata element. A cell in a spreadsheet
17 | // application can have metadata associated with it. Metadata is just a set of
18 | // additional properties about the particular cell, and this metadata is stored
19 | // in the metadata xml part. There are two types of metadata: cell metadata and
20 | // value metadata. Cell metadata contains information about the cell itself,
21 | // and this metadata can be carried along with the cell as it moves
22 | // (insert, shift, copy/paste, merge, unmerge, etc). Value metadata is
23 | // information about the value of a particular cell. Value metadata properties
24 | // can be propagated along with the value as it is referenced in formulas.
25 | type xlsxMetadata struct {
26 | XMLName xml.Name `xml:"metadata"`
27 | MetadataTypes *xlsxInnerXML `xml:"metadataTypes"`
28 | MetadataStrings *xlsxInnerXML `xml:"metadataStrings"`
29 | MdxMetadata *xlsxInnerXML `xml:"mdxMetadata"`
30 | FutureMetadata []xlsxFutureMetadata `xml:"futureMetadata"`
31 | CellMetadata *xlsxMetadataBlocks `xml:"cellMetadata"`
32 | ValueMetadata *xlsxMetadataBlocks `xml:"valueMetadata"`
33 | ExtLst *xlsxInnerXML `xml:"extLst"`
34 | }
35 |
36 | // xlsxFutureMetadata directly maps the futureMetadata element. This element
37 | // represents future metadata information.
38 | type xlsxFutureMetadata struct {
39 | Bk []xlsxFutureMetadataBlock `xml:"bk"`
40 | ExtLst *xlsxInnerXML `xml:"extLst"`
41 | }
42 |
43 | // xlsxFutureMetadataBlock directly maps the kb element. This element represents
44 | // a block of future metadata information. This is a location for storing
45 | // feature extension information.
46 | type xlsxFutureMetadataBlock struct {
47 | ExtLst *xlsxInnerXML `xml:"extLst"`
48 | }
49 |
50 | // xlsxMetadataBlocks directly maps the metadata element. This element
51 | // represents cell metadata information. Cell metadata is information metadata
52 | // about a specific cell, and it stays tied to that cell position.
53 | type xlsxMetadataBlocks struct {
54 | Count int `xml:"count,attr,omitempty"`
55 | Bk []xlsxMetadataBlock `xml:"bk"`
56 | }
57 |
58 | // xlsxMetadataBlock directly maps the bk element. This element represents a
59 | // block of metadata records.
60 | type xlsxMetadataBlock struct {
61 | Rc []xlsxMetadataRecord `xml:"rc"`
62 | }
63 |
64 | // xlsxMetadataRecord directly maps the rc element. This element represents a
65 | // reference to a specific metadata record.
66 | type xlsxMetadataRecord struct {
67 | T int `xml:"t,attr"`
68 | V int `xml:"v,attr"`
69 | }
70 |
71 | // xlsxRichValueData directly maps the rvData element that specifies rich value
72 | // data.
73 | type xlsxRichValueData struct {
74 | XMLName xml.Name `xml:"rvData"`
75 | Count int `xml:"count,attr,omitempty"`
76 | Rv []xlsxRichValue `xml:"rv"`
77 | ExtLst *xlsxInnerXML `xml:"extLst"`
78 | }
79 |
80 | // xlsxRichValue directly maps the rv element that specifies rich value data
81 | // information for a single rich value
82 | type xlsxRichValue struct {
83 | S int `xml:"s,attr"`
84 | V []string `xml:"v"`
85 | Fb *xlsxInnerXML `xml:"fb"`
86 | }
87 |
88 | // xlsxRichValueRels directly maps the richValueRels element. This element that
89 | // specifies a list of rich value relationships.
90 | type xlsxRichValueRels struct {
91 | XMLName xml.Name `xml:"richValueRels"`
92 | Rels []xlsxRichValueRelRelationship `xml:"rel"`
93 | ExtLst *xlsxInnerXML `xml:"extLst"`
94 | }
95 |
96 | // xlsxRichValueRelRelationship directly maps the rel element. This element
97 | // specifies a relationship for a rich value property.
98 | type xlsxRichValueRelRelationship struct {
99 | ID string `xml:"id,attr"`
100 | }
101 |
102 | // xlsxWebImagesSupportingRichData directly maps the webImagesSrd element. This
103 | // element specifies a list of sets of properties associated with web image rich
104 | // values.
105 | type xlsxWebImagesSupportingRichData struct {
106 | XMLName xml.Name `xml:"webImagesSrd"`
107 | WebImageSrd []xlsxWebImageSupportingRichData `xml:"webImageSrd"`
108 | ExtLst *xlsxInnerXML `xml:"extLst"`
109 | }
110 |
111 | // xlsxWebImageSupportingRichData directly maps the webImageSrd element. This
112 | // element specifies a set of properties for a web image rich value.
113 | type xlsxWebImageSupportingRichData struct {
114 | Address xlsxExternalReference `xml:"address"`
115 | MoreImagesAddress xlsxExternalReference `xml:"moreImagesAddress"`
116 | Blip xlsxExternalReference `xml:"blip"`
117 | }
118 |
--------------------------------------------------------------------------------
/xmlSharedStrings.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 - 2025 The excelize Authors. All rights reserved. Use of
2 | // this source code is governed by a BSD-style license that can be found in
3 | // the LICENSE file.
4 | //
5 | // Package excelize providing a set of functions that allow you to write to and
6 | // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
7 | // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
8 | // Supports complex components by high compatibility, and provided streaming
9 | // API for generating or reading data from a worksheet with huge amounts of
10 | // data. This library needs Go version 1.23 or later.
11 |
12 | package excelize
13 |
14 | import (
15 | "encoding/xml"
16 | "sync"
17 | )
18 |
19 | // xlsxSST directly maps the sst element from the namespace
20 | // http://schemas.openxmlformats.org/spreadsheetml/2006/main. String values may
21 | // be stored directly inside spreadsheet cell elements; however, storing the
22 | // same value inside multiple cell elements can result in very large worksheet
23 | // Parts, possibly resulting in performance degradation. The Shared String Table
24 | // is an indexed list of string values, shared across the workbook, which allows
25 | // implementations to store values only once.
26 | type xlsxSST struct {
27 | mu sync.Mutex
28 | XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main sst"`
29 | Count int `xml:"count,attr"`
30 | UniqueCount int `xml:"uniqueCount,attr"`
31 | SI []xlsxSI `xml:"si"`
32 | }
33 |
34 | // xlsxSI (String Item) is the representation of an individual string in the
35 | // Shared String table. If the string is just a simple string with formatting
36 | // applied at the cell level, then the String Item (si) should contain a
37 | // single text element used to express the string. However, if the string in
38 | // the cell is more complex - i.e., has formatting applied at the character
39 | // level - then the string item shall consist of multiple rich text runs which
40 | // collectively are used to express the string.
41 | type xlsxSI struct {
42 | T *xlsxT `xml:"t,omitempty"`
43 | R []xlsxR `xml:"r"`
44 | RPh []*xlsxPhoneticRun `xml:"rPh"`
45 | PhoneticPr *xlsxPhoneticPr `xml:"phoneticPr"`
46 | }
47 |
48 | // xlsxR represents a run of rich text. A rich text run is a region of text
49 | // that share a common set of properties, such as formatting properties. The
50 | // properties are defined in the rPr element, and the text displayed to the
51 | // user is defined in the Text (t) element.
52 | type xlsxR struct {
53 | XMLName xml.Name `xml:"r"`
54 | RPr *xlsxRPr `xml:"rPr"`
55 | T *xlsxT `xml:"t"`
56 | }
57 |
58 | // xlsxT directly maps the t element in the run properties.
59 | type xlsxT struct {
60 | XMLName xml.Name `xml:"t"`
61 | Space xml.Attr `xml:"space,attr,omitempty"`
62 | Val string `xml:",chardata"`
63 | }
64 |
65 | // xlsxRPr (Run Properties) specifies a set of run properties which shall be
66 | // applied to the contents of the parent run after all style formatting has been
67 | // applied to the text. These properties are defined as direct formatting, since
68 | // they are directly applied to the run and supersede any formatting from
69 | // styles.
70 | type xlsxRPr struct {
71 | RFont *attrValString `xml:"rFont"`
72 | Charset *attrValInt `xml:"charset"`
73 | Family *attrValInt `xml:"family"`
74 | B *attrValBool `xml:"b"`
75 | I *attrValBool `xml:"i"`
76 | Strike *attrValBool `xml:"strike"`
77 | Outline *attrValBool `xml:"outline"`
78 | Shadow *attrValBool `xml:"shadow"`
79 | Condense *attrValBool `xml:"condense"`
80 | Extend *attrValBool `xml:"extend"`
81 | Color *xlsxColor `xml:"color"`
82 | Sz *attrValFloat `xml:"sz"`
83 | U *attrValString `xml:"u"`
84 | VertAlign *attrValString `xml:"vertAlign"`
85 | Scheme *attrValString `xml:"scheme"`
86 | }
87 |
88 | // RichTextRun directly maps the settings of the rich text run.
89 | type RichTextRun struct {
90 | Font *Font
91 | Text string
92 | }
93 |
--------------------------------------------------------------------------------
/xmlSlicers.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 - 2025 The excelize Authors. All rights reserved. Use of
2 | // this source code is governed by a BSD-style license that can be found in
3 | // the LICENSE file.
4 | //
5 | // Package excelize providing a set of functions that allow you to write to and
6 | // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
7 | // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
8 | // Supports complex components by high compatibility, and provided streaming
9 | // API for generating or reading data from a worksheet with huge amounts of
10 | // data. This library needs Go version 1.23 or later.
11 |
12 | package excelize
13 |
14 | import "encoding/xml"
15 |
16 | // xlsxSlicers directly maps the slicers element that specifies a slicer view on
17 | // the worksheet.
18 | type xlsxSlicers struct {
19 | XMLName xml.Name `xml:"http://schemas.microsoft.com/office/spreadsheetml/2009/9/main slicers"`
20 | XMLNSXMC string `xml:"xmlns:mc,attr"`
21 | XMLNSX string `xml:"xmlns:x,attr"`
22 | XMLNSXR10 string `xml:"xmlns:xr10,attr"`
23 | Slicer []xlsxSlicer `xml:"slicer"`
24 | }
25 |
26 | // xlsxSlicer is a complex type that specifies a slicer view.
27 | type xlsxSlicer struct {
28 | Name string `xml:"name,attr"`
29 | XR10UID string `xml:"xr10:uid,attr,omitempty"`
30 | Cache string `xml:"cache,attr"`
31 | Caption string `xml:"caption,attr,omitempty"`
32 | StartItem *int `xml:"startItem,attr"`
33 | ColumnCount *int `xml:"columnCount,attr"`
34 | ShowCaption *bool `xml:"showCaption,attr"`
35 | Level int `xml:"level,attr,omitempty"`
36 | Style string `xml:"style,attr,omitempty"`
37 | LockedPosition bool `xml:"lockedPosition,attr,omitempty"`
38 | RowHeight int `xml:"rowHeight,attr"`
39 | }
40 |
41 | // slicerCacheDefinition directly maps the slicerCacheDefinition element that
42 | // specifies a slicer cache.
43 | type xlsxSlicerCacheDefinition struct {
44 | XMLName xml.Name `xml:"http://schemas.microsoft.com/office/spreadsheetml/2009/9/main slicerCacheDefinition"`
45 | XMLNSXMC string `xml:"xmlns:mc,attr"`
46 | XMLNSX string `xml:"xmlns:x,attr"`
47 | XMLNSX15 string `xml:"xmlns:x15,attr,omitempty"`
48 | XMLNSXR10 string `xml:"xmlns:xr10,attr"`
49 | Name string `xml:"name,attr"`
50 | XR10UID string `xml:"xr10:uid,attr,omitempty"`
51 | SourceName string `xml:"sourceName,attr"`
52 | PivotTables *xlsxSlicerCachePivotTables `xml:"pivotTables"`
53 | Data *xlsxSlicerCacheData `xml:"data"`
54 | ExtLst *xlsxExtLst `xml:"extLst"`
55 | }
56 |
57 | // xlsxSlicerCachePivotTables is a complex type that specifies a group of
58 | // pivotTable elements that specify the PivotTable views that are filtered by
59 | // the slicer cache.
60 | type xlsxSlicerCachePivotTables struct {
61 | PivotTable []xlsxSlicerCachePivotTable `xml:"pivotTable"`
62 | }
63 |
64 | // xlsxSlicerCachePivotTable is a complex type that specifies a PivotTable view
65 | // filtered by a slicer cache.
66 | type xlsxSlicerCachePivotTable struct {
67 | TabID int `xml:"tabId,attr"`
68 | Name string `xml:"name,attr"`
69 | }
70 |
71 | // xlsxSlicerCacheData is a complex type that specifies a data source for the
72 | // slicer cache.
73 | type xlsxSlicerCacheData struct {
74 | OLAP *xlsxInnerXML `xml:"olap"`
75 | Tabular *xlsxTabularSlicerCache `xml:"tabular"`
76 | }
77 |
78 | // xlsxTabularSlicerCache is a complex type that specifies non-OLAP slicer items
79 | // that are cached within this slicer cache and properties of the slicer cache
80 | // specific to non-OLAP slicer items.
81 | type xlsxTabularSlicerCache struct {
82 | PivotCacheID int `xml:"pivotCacheId,attr"`
83 | SortOrder string `xml:"sortOrder,attr,omitempty"`
84 | CustomListSort *bool `xml:"customListSort,attr"`
85 | ShowMissing *bool `xml:"showMissing,attr"`
86 | CrossFilter string `xml:"crossFilter,attr,omitempty"`
87 | Items *xlsxTabularSlicerCacheItems `xml:"items"`
88 | ExtLst *xlsxExtLst `xml:"extLst"`
89 | }
90 |
91 | // xlsxTabularSlicerCacheItems is a complex type that specifies non-OLAP slicer
92 | // items that are cached within this slicer cache.
93 | type xlsxTabularSlicerCacheItems struct {
94 | Count int `xml:"count,attr,omitempty"`
95 | I []xlsxTabularSlicerCacheItem `xml:"i"`
96 | }
97 |
98 | // xlsxTabularSlicerCacheItem is a complex type that specifies a non-OLAP slicer
99 | // item that is cached within this slicer cache.
100 | type xlsxTabularSlicerCacheItem struct {
101 | X int `xml:"x,attr"`
102 | S bool `xml:"s,attr,omitempty"`
103 | ND bool `xml:"nd,attr,omitempty"`
104 | }
105 |
106 | // xlsxTableSlicerCache specifies a table data source for the slicer cache.
107 | type xlsxTableSlicerCache struct {
108 | XMLName xml.Name `xml:"x15:tableSlicerCache"`
109 | TableID int `xml:"tableId,attr"`
110 | Column int `xml:"column,attr"`
111 | SortOrder string `xml:"sortOrder,attr,omitempty"`
112 | CustomListSort *bool `xml:"customListSort,attr"`
113 | CrossFilter string `xml:"crossFilter,attr,omitempty"`
114 | ExtLst *xlsxExtLst `xml:"extLst"`
115 | }
116 |
117 | // xlsxX14SlicerList specifies a list of slicer.
118 | type xlsxX14SlicerList struct {
119 | XMLName xml.Name `xml:"x14:slicerList"`
120 | Slicer []*xlsxX14Slicer `xml:"x14:slicer"`
121 | }
122 |
123 | // xlsxX14Slicer specifies a slicer view,
124 | type xlsxX14Slicer struct {
125 | XMLName xml.Name `xml:"x14:slicer"`
126 | RID string `xml:"r:id,attr"`
127 | }
128 |
129 | // xlsxX14SlicerCaches directly maps the x14:slicerCache element.
130 | type xlsxX14SlicerCaches struct {
131 | XMLName xml.Name `xml:"x14:slicerCaches"`
132 | XMLNS string `xml:"xmlns:x14,attr"`
133 | Content string `xml:",innerxml"`
134 | }
135 |
136 | // xlsxX15SlicerCaches directly maps the x14:slicerCache element.
137 | type xlsxX14SlicerCache struct {
138 | XMLName xml.Name `xml:"x14:slicerCache"`
139 | RID string `xml:"r:id,attr"`
140 | }
141 |
142 | // xlsxX15SlicerCaches directly maps the x15:slicerCaches element.
143 | type xlsxX15SlicerCaches struct {
144 | XMLName xml.Name `xml:"x15:slicerCaches"`
145 | XMLNS string `xml:"xmlns:x14,attr"`
146 | Content string `xml:",innerxml"`
147 | }
148 |
149 | // decodeTableSlicerCache defines the structure used to parse the
150 | // x15:tableSlicerCache element of the table slicer cache.
151 | type decodeTableSlicerCache struct {
152 | XMLName xml.Name `xml:"tableSlicerCache"`
153 | TableID int `xml:"tableId,attr"`
154 | Column int `xml:"column,attr"`
155 | SortOrder string `xml:"sortOrder,attr"`
156 | }
157 |
158 | // decodeSlicerList defines the structure used to parse the x14:slicerList
159 | // element of a list of slicer.
160 | type decodeSlicerList struct {
161 | XMLName xml.Name `xml:"slicerList"`
162 | Slicer []*decodeSlicer `xml:"slicer"`
163 | }
164 |
165 | // decodeSlicer defines the structure used to parse the x14:slicer element of a
166 | // slicer.
167 | type decodeSlicer struct {
168 | RID string `xml:"id,attr"`
169 | }
170 |
171 | // decodeSlicerCaches defines the structure used to parse the
172 | // x14:slicerCaches and x15:slicerCaches element of a slicer cache.
173 | type decodeSlicerCaches struct {
174 | XMLName xml.Name `xml:"slicerCaches"`
175 | Content string `xml:",innerxml"`
176 | }
177 |
178 | // xlsxTimelines is a mechanism for filtering data in pivot table views, cube
179 | // functions and charts based on non-worksheet pivot tables. In the case of
180 | // using OLAP Timeline source data, a Timeline is based on a key attribute of
181 | // an OLAP hierarchy. In the case of using native Timeline source data, a
182 | // Timeline is based on a data table column.
183 | type xlsxTimelines struct {
184 | XMLName xml.Name `xml:"http://schemas.microsoft.com/office/spreadsheetml/2010/11/main timelines"`
185 | XMLNSXMC string `xml:"xmlns:mc,attr"`
186 | XMLNSX string `xml:"xmlns:x,attr"`
187 | XMLNSXR10 string `xml:"xmlns:xr10,attr"`
188 | Timeline []xlsxTimeline `xml:"timeline"`
189 | }
190 |
191 | // xlsxTimeline is timeline view specifies the display of a timeline on a
192 | // worksheet.
193 | type xlsxTimeline struct {
194 | Name string `xml:"name,attr"`
195 | XR10UID string `xml:"xr10:uid,attr,omitempty"`
196 | Cache string `xml:"cache,attr"`
197 | Caption string `xml:"caption,attr,omitempty"`
198 | ShowHeader *bool `xml:"showHeader,attr"`
199 | ShowSelectionLabel *bool `xml:"showSelectionLabel,attr"`
200 | ShowTimeLevel *bool `xml:"showTimeLevel,attr"`
201 | ShowHorizontalScrollbar *bool `xml:"showHorizontalScrollbar,attr"`
202 | Level int `xml:"level,attr"`
203 | SelectionLevel int `xml:"selectionLevel,attr"`
204 | ScrollPosition string `xml:"scrollPosition,attr,omitempty"`
205 | Style string `xml:"style,attr,omitempty"`
206 | }
207 |
--------------------------------------------------------------------------------
/xmlTheme.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 - 2025 The excelize Authors. All rights reserved. Use of
2 | // this source code is governed by a BSD-style license that can be found in
3 | // the LICENSE file.
4 | //
5 | // Package excelize providing a set of functions that allow you to write to and
6 | // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
7 | // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
8 | // Supports complex components by high compatibility, and provided streaming
9 | // API for generating or reading data from a worksheet with huge amounts of
10 | // data. This library needs Go version 1.23 or later.
11 |
12 | package excelize
13 |
14 | import "encoding/xml"
15 |
16 | // xlsxTheme directly maps the theme element in the namespace
17 | // http://schemas.openxmlformats.org/drawingml/2006/main
18 | type xlsxTheme struct {
19 | XMLName xml.Name `xml:"a:theme"`
20 | XMLNSa string `xml:"xmlns:a,attr"`
21 | XMLNSr string `xml:"xmlns:r,attr"`
22 | Name string `xml:"name,attr"`
23 | ThemeElements xlsxBaseStyles `xml:"a:themeElements"`
24 | ObjectDefaults xlsxObjectDefaults `xml:"a:objectDefaults"`
25 | ExtraClrSchemeLst xlsxExtraClrSchemeLst `xml:"a:extraClrSchemeLst"`
26 | CustClrLst *xlsxInnerXML `xml:"a:custClrLst"`
27 | ExtLst *xlsxExtLst `xml:"a:extLst"`
28 | }
29 |
30 | // xlsxBaseStyles defines the theme elements for a theme, and is the workhorse
31 | // of the theme. The bulk of the shared theme information that is used by a
32 | // given document is defined here. Within this complex type is defined a color
33 | // scheme, a font scheme, and a style matrix (format scheme) that defines
34 | // different formatting options for different pieces of a document.
35 | type xlsxBaseStyles struct {
36 | ClrScheme xlsxColorScheme `xml:"a:clrScheme"`
37 | FontScheme xlsxFontScheme `xml:"a:fontScheme"`
38 | FmtScheme xlsxStyleMatrix `xml:"a:fmtScheme"`
39 | ExtLst *xlsxExtLst `xml:"a:extLst"`
40 | }
41 |
42 | // xlsxCTColor holds the actual color values that are to be applied to a given
43 | // diagram and how those colors are to be applied.
44 | type xlsxCTColor struct {
45 | ScrgbClr *xlsxInnerXML `xml:"a:scrgbClr"`
46 | SrgbClr *attrValString `xml:"a:srgbClr"`
47 | HslClr *xlsxInnerXML `xml:"a:hslClr"`
48 | SysClr *xlsxSysClr `xml:"a:sysClr"`
49 | SchemeClr *xlsxInnerXML `xml:"a:schemeClr"`
50 | PrstClr *xlsxInnerXML `xml:"a:prstClr"`
51 | }
52 |
53 | // xlsxColorScheme defines a set of colors for the theme. The set of colors
54 | // consists of twelve color slots that can each hold a color of choice.
55 | type xlsxColorScheme struct {
56 | Name string `xml:"name,attr"`
57 | Dk1 xlsxCTColor `xml:"a:dk1"`
58 | Lt1 xlsxCTColor `xml:"a:lt1"`
59 | Dk2 xlsxCTColor `xml:"a:dk2"`
60 | Lt2 xlsxCTColor `xml:"a:lt2"`
61 | Accent1 xlsxCTColor `xml:"a:accent1"`
62 | Accent2 xlsxCTColor `xml:"a:accent2"`
63 | Accent3 xlsxCTColor `xml:"a:accent3"`
64 | Accent4 xlsxCTColor `xml:"a:accent4"`
65 | Accent5 xlsxCTColor `xml:"a:accent5"`
66 | Accent6 xlsxCTColor `xml:"a:accent6"`
67 | Hlink xlsxCTColor `xml:"a:hlink"`
68 | FolHlink xlsxCTColor `xml:"a:folHlink"`
69 | ExtLst *xlsxExtLst `xml:"a:extLst"`
70 | }
71 |
72 | // objectDefaults element allows for the definition of default shape, line,
73 | // and textbox formatting properties. An application can use this information
74 | // to format a shape (or text) initially on insertion into a document.
75 | type xlsxObjectDefaults struct {
76 | ObjectDefaults string `xml:",innerxml"`
77 | }
78 |
79 | // xlsxExtraClrSchemeLst element is a container for the list of extra color
80 | // schemes present in a document.
81 | type xlsxExtraClrSchemeLst struct {
82 | ExtraClrSchemeLst string `xml:",innerxml"`
83 | }
84 |
85 | // xlsxCTSupplementalFont defines an additional font that is used for language
86 | // specific fonts in themes. For example, one can specify a font that gets used
87 | // only within the Japanese language context.
88 | type xlsxCTSupplementalFont struct {
89 | Script string `xml:"script,attr"`
90 | Typeface string `xml:"typeface,attr"`
91 | }
92 |
93 | // xlsxFontCollection defines a major and minor font which is used in the font
94 | // scheme. A font collection consists of a font definition for Latin, East
95 | // Asian, and complex script. On top of these three definitions, one can also
96 | // define a font for use in a specific language or languages.
97 | type xlsxFontCollection struct {
98 | Latin *xlsxCTTextFont `xml:"a:latin"`
99 | Ea *xlsxCTTextFont `xml:"a:ea"`
100 | Cs *xlsxCTTextFont `xml:"a:cs"`
101 | Font []xlsxCTSupplementalFont `xml:"a:font"`
102 | ExtLst *xlsxExtLst `xml:"a:extLst"`
103 | }
104 |
105 | // xlsxFontScheme element defines the font scheme within the theme. The font
106 | // scheme consists of a pair of major and minor fonts for which to use in a
107 | // document. The major font corresponds well with the heading areas of a
108 | // document, and the minor font corresponds well with the normal text or
109 | // paragraph areas.
110 | type xlsxFontScheme struct {
111 | Name string `xml:"name,attr"`
112 | MajorFont xlsxFontCollection `xml:"a:majorFont"`
113 | MinorFont xlsxFontCollection `xml:"a:minorFont"`
114 | ExtLst *xlsxExtLst `xml:"a:extLst"`
115 | }
116 |
117 | // xlsxStyleMatrix defines a set of formatting options, which can be referenced
118 | // by documents that apply a certain style to a given part of an object. For
119 | // example, in a given shape, say a rectangle, one can reference a themed line
120 | // style, themed effect, and themed fill that would be theme specific and
121 | // change when the theme is changed.
122 | type xlsxStyleMatrix struct {
123 | Name string `xml:"name,attr,omitempty"`
124 | FillStyleLst xlsxFillStyleLst `xml:"a:fillStyleLst"`
125 | LnStyleLst xlsxLnStyleLst `xml:"a:lnStyleLst"`
126 | EffectStyleLst xlsxEffectStyleLst `xml:"a:effectStyleLst"`
127 | BgFillStyleLst xlsxBgFillStyleLst `xml:"a:bgFillStyleLst"`
128 | }
129 |
130 | // xlsxFillStyleLst element defines a set of three fill styles that are used
131 | // within a theme. The three fill styles are arranged in order from subtle to
132 | // moderate to intense.
133 | type xlsxFillStyleLst struct {
134 | FillStyleLst string `xml:",innerxml"`
135 | }
136 |
137 | // xlsxLnStyleLst element defines a list of three line styles for use within a
138 | // theme. The three line styles are arranged in order from subtle to moderate
139 | // to intense versions of lines. This list makes up part of the style matrix.
140 | type xlsxLnStyleLst struct {
141 | LnStyleLst string `xml:",innerxml"`
142 | }
143 |
144 | // xlsxEffectStyleLst element defines a set of three effect styles that create
145 | // the effect style list for a theme. The effect styles are arranged in order
146 | // of subtle to moderate to intense.
147 | type xlsxEffectStyleLst struct {
148 | EffectStyleLst string `xml:",innerxml"`
149 | }
150 |
151 | // xlsxBgFillStyleLst element defines a list of background fills that are
152 | // used within a theme. The background fills consist of three fills, arranged
153 | // in order from subtle to moderate to intense.
154 | type xlsxBgFillStyleLst struct {
155 | BgFillStyleLst string `xml:",innerxml"`
156 | }
157 |
158 | // xlsxSysClr element specifies a color bound to predefined operating system
159 | // elements.
160 | type xlsxSysClr struct {
161 | Val string `xml:"val,attr"`
162 | LastClr string `xml:"lastClr,attr"`
163 | }
164 |
165 | // decodeTheme defines the structure used to parse the a:theme element for the
166 | // theme.
167 | type decodeTheme struct {
168 | XMLName xml.Name `xml:"http://schemas.openxmlformats.org/drawingml/2006/main theme"`
169 | Name string `xml:"name,attr"`
170 | ThemeElements decodeBaseStyles `xml:"themeElements"`
171 | ObjectDefaults xlsxObjectDefaults `xml:"objectDefaults"`
172 | ExtraClrSchemeLst xlsxExtraClrSchemeLst `xml:"extraClrSchemeLst"`
173 | CustClrLst *xlsxInnerXML `xml:"custClrLst"`
174 | ExtLst *xlsxExtLst `xml:"extLst"`
175 | }
176 |
177 | // decodeBaseStyles defines the structure used to parse the theme elements for a
178 | // theme, and is the workhorse of the theme.
179 | type decodeBaseStyles struct {
180 | ClrScheme decodeColorScheme `xml:"clrScheme"`
181 | FontScheme decodeFontScheme `xml:"fontScheme"`
182 | FmtScheme decodeStyleMatrix `xml:"fmtScheme"`
183 | ExtLst *xlsxExtLst `xml:"extLst"`
184 | }
185 |
186 | // decodeColorScheme defines the structure used to parse a set of colors for the
187 | // theme.
188 | type decodeColorScheme struct {
189 | Name string `xml:"name,attr"`
190 | Dk1 decodeCTColor `xml:"dk1"`
191 | Lt1 decodeCTColor `xml:"lt1"`
192 | Dk2 decodeCTColor `xml:"dk2"`
193 | Lt2 decodeCTColor `xml:"lt2"`
194 | Accent1 decodeCTColor `xml:"accent1"`
195 | Accent2 decodeCTColor `xml:"accent2"`
196 | Accent3 decodeCTColor `xml:"accent3"`
197 | Accent4 decodeCTColor `xml:"accent4"`
198 | Accent5 decodeCTColor `xml:"accent5"`
199 | Accent6 decodeCTColor `xml:"accent6"`
200 | Hlink decodeCTColor `xml:"hlink"`
201 | FolHlink decodeCTColor `xml:"folHlink"`
202 | ExtLst *xlsxExtLst `xml:"extLst"`
203 | }
204 |
205 | // decodeFontScheme defines the structure used to parse font scheme within the
206 | // theme.
207 | type decodeFontScheme struct {
208 | Name string `xml:"name,attr"`
209 | MajorFont decodeFontCollection `xml:"majorFont"`
210 | MinorFont decodeFontCollection `xml:"minorFont"`
211 | ExtLst *xlsxExtLst `xml:"extLst"`
212 | }
213 |
214 | // decodeFontCollection defines the structure used to parse a major and minor
215 | // font which is used in the font scheme.
216 | type decodeFontCollection struct {
217 | Latin *xlsxCTTextFont `xml:"latin"`
218 | Ea *xlsxCTTextFont `xml:"ea"`
219 | Cs *xlsxCTTextFont `xml:"cs"`
220 | Font []xlsxCTSupplementalFont `xml:"font"`
221 | ExtLst *xlsxExtLst `xml:"extLst"`
222 | }
223 |
224 | // decodeCTColor defines the structure used to parse the actual color values
225 | // that are to be applied to a given diagram and how those colors are to be
226 | // applied.
227 | type decodeCTColor struct {
228 | ScrgbClr *xlsxInnerXML `xml:"scrgbClr"`
229 | SrgbClr *attrValString `xml:"srgbClr"`
230 | HslClr *xlsxInnerXML `xml:"hslClr"`
231 | SysClr *xlsxSysClr `xml:"sysClr"`
232 | SchemeClr *xlsxInnerXML `xml:"schemeClr"`
233 | PrstClr *xlsxInnerXML `xml:"prstClr"`
234 | }
235 |
236 | // decodeStyleMatrix defines the structure used to parse a set of formatting
237 | // options, which can be referenced by documents that apply a certain style to
238 | // a given part of an object.
239 | type decodeStyleMatrix struct {
240 | Name string `xml:"name,attr,omitempty"`
241 | FillStyleLst xlsxFillStyleLst `xml:"fillStyleLst"`
242 | LnStyleLst xlsxLnStyleLst `xml:"lnStyleLst"`
243 | EffectStyleLst xlsxEffectStyleLst `xml:"effectStyleLst"`
244 | BgFillStyleLst xlsxBgFillStyleLst `xml:"bgFillStyleLst"`
245 | }
246 |
--------------------------------------------------------------------------------