├── .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 |

Excelize logo

2 | 3 |

4 | Build Status 5 | Code Coverage 6 | Go Report Card 7 | go.dev 8 | Licenses 9 | Donate 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 |

Excelize

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 |

Excelize logo

2 | 3 |

4 | Build Status 5 | Code Coverage 6 | Go Report Card 7 | go.dev 8 | Licenses 9 | Donate 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 |

使用 Excelize 在 Excel 电子表格文档中创建图表

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 | --------------------------------------------------------------------------------