hongzhenyue il y a 4 ans
Parent
commit
0397a1277b
83 fichiers modifiés avec 1916 ajouts et 16429 suppressions
  1. 0 71
      .github/workflows/codeql-analysis.yml
  2. 0 5
      .gitignore
  3. 6 5
      .travis.yml
  4. 1 1
      LICENSE
  5. 23 60
      README.md
  6. 13 51
      README_zh.md
  7. 58 61
      adjust.go
  8. 4 12
      adjust_test.go
  9. 0 8067
      calc.go
  10. 0 2597
      calc_test.go
  11. 4 6
      calcchain.go
  12. 88 216
      cell.go
  13. 1 175
      cell_test.go
  14. 26 183
      chart.go
  15. 21 136
      chart_test.go
  16. 99 276
      col.go
  17. 6 173
      col_test.go
  18. 20 30
      comment.go
  19. 2 3
      comment_test.go
  20. 0 616
      crypt.go
  21. 0 35
      crypt_test.go
  22. 7 9
      datavalidation.go
  23. 2 2
      datavalidation_test.go
  24. 6 7
      date.go
  25. 2 2
      date_test.go
  26. 4 6
      docProps.go
  27. 6 8
      docProps_test.go
  28. 51 64
      drawing.go
  29. 5 7
      drawing_test.go
  30. 5 51
      errors.go
  31. 55 100
      excelize.go
  32. 78 101
      excelize_test.go
  33. 8 53
      file.go
  34. 3 3
      file_test.go
  35. 6 8
      go.mod
  36. 13 25
      go.sum
  37. 40 233
      lib.go
  38. 1 27
      lib_test.go
  39. 21 28
      merge.go
  40. 2 2
      merge_test.go
  41. 26 85
      picture.go
  42. 3 30
      picture_test.go
  43. 41 139
      pivotTable.go
  44. 32 105
      pivotTable_test.go
  45. 140 227
      rows.go
  46. 108 175
      rows_test.go
  47. 8 11
      shape.go
  48. 157 347
      sheet.go
  49. 55 164
      sheet_test.go
  50. 6 227
      sheetpr.go
  51. 87 254
      sheetpr_test.go
  52. 10 14
      sheetview.go
  53. 47 45
      sheetview_test.go
  54. 7 9
      sparkline.go
  55. 2 2
      sparkline_test.go
  56. 76 138
      stream.go
  57. 9 47
      stream_test.go
  58. 108 370
      styles.go
  59. 13 98
      styles_test.go
  60. 41 74
      table.go
  61. 1 7
      table_test.go
  62. 4 6
      templates.go
  63. BIN
      test/Book1.xlsx
  64. BIN
      test/encryptAES.xlsx
  65. BIN
      test/encryptSHA1.xlsx
  66. 4 6
      vmlDrawing.go
  67. 4 6
      xmlApp.go
  68. 4 6
      xmlCalcChain.go
  69. 21 24
      xmlChart.go
  70. 23 23
      xmlChartSheet.go
  71. 7 9
      xmlComments.go
  72. 4 6
      xmlContentTypes.go
  73. 4 6
      xmlCore.go
  74. 4 6
      xmlDecodeDrawing.go
  75. 17 47
      xmlDrawing.go
  76. 0 25
      xmlPivotCache.go
  77. 14 16
      xmlPivotTable.go
  78. 16 23
      xmlSharedStrings.go
  79. 46 41
      xmlStyles.go
  80. 7 9
      xmlTable.go
  81. 7 9
      xmlTheme.go
  82. 9 19
      xmlWorkbook.go
  83. 57 59
      xmlWorksheet.go

+ 0 - 71
.github/workflows/codeql-analysis.yml

@@ -1,71 +0,0 @@
-# For most projects, this workflow file will not need changing; you simply need
-# to commit it to your repository.
-#
-# You may wish to alter this file to override the set of languages analyzed,
-# or to provide custom queries or build logic.
-name: "CodeQL"
-
-on:
-  push:
-    branches: [master]
-  pull_request:
-    # The branches below must be a subset of the branches above
-    branches: [master]
-  schedule:
-    - cron: '0 6 * * 3'
-
-jobs:
-  analyze:
-    name: Analyze
-    runs-on: ubuntu-latest
-
-    strategy:
-      fail-fast: false
-      matrix:
-        # Override automatic language detection by changing the below list
-        # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
-        language: ['go']
-        # Learn more...
-        # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
-
-    steps:
-    - name: Checkout repository
-      uses: actions/checkout@v2
-      with:
-        # We must fetch at least the immediate parents so that if this is
-        # a pull request then we can checkout the head.
-        fetch-depth: 2
-
-    # If this run was triggered by a pull request event, then checkout
-    # the head of the pull request instead of the merge commit.
-    - run: git checkout HEAD^2
-      if: ${{ github.event_name == 'pull_request' }}
-
-    # Initializes the CodeQL tools for scanning.
-    - name: Initialize CodeQL
-      uses: github/codeql-action/init@v1
-      with:
-        languages: ${{ matrix.language }}
-        # If you wish to specify custom queries, you can do so here or in a config file.
-        # By default, queries listed here will override any specified in a config file. 
-        # Prefix the list here with "+" to use these queries and those in the config file.
-        # queries: ./path/to/local/query, your-org/your-repo/queries@main
-
-    # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
-    # If this step fails, then you should remove it and run the build manually (see below)
-    - name: Autobuild
-      uses: github/codeql-action/autobuild@v1
-
-    # ℹ️ Command-line programs to run using the OS shell.
-    # 📚 https://git.io/JvXDl
-
-    # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
-    #    and modify them (or add more) to build your code if your project
-    #    uses a compiled language
-
-    #- run: |
-    #   make bootstrap
-    #   make release
-
-    - name: Perform CodeQL Analysis
-      uses: github/codeql-action/analyze@v1

+ 0 - 5
.gitignore

@@ -1,10 +1,5 @@
 ~$*.xlsx
 test/Test*.xlsx
-test/Test*.xlsm
-# generated files
-test/BadEncrypt.xlsx
-test/BadWorkbook.SaveAsEmptyStruct.xlsx
-test/*.png
 *.out
 *.test
 .idea

+ 6 - 5
.travis.yml

@@ -4,13 +4,14 @@ install:
   - go get -d -t -v ./... && go build -v ./...
 
 go:
-  - 1.15.x
-  - 1.16.x
+  - 1.11.x
+  - 1.12.x
+  - 1.13.x
+  - 1.14.x
 
 os:
   - linux
   - osx
-  - windows
 
 env:
   jobs:
@@ -18,8 +19,8 @@ env:
     - GOARCH=386
 
 script:
-  - env GO111MODULE=on go vet ./...
-  - env GO111MODULE=on go test -v -race ./... -coverprofile=coverage.txt -covermode=atomic
+  - go vet ./...
+  - go test ./... -v -coverprofile=coverage.txt -covermode=atomic
 
 after_success:
   - bash <(curl -s https://codecov.io/bash)

+ 1 - 1
LICENSE

@@ -1,6 +1,6 @@
 BSD 3-Clause License
 
-Copyright (c) 2016-2021 The excelize Authors.
+Copyright (c) 2016-2020 The excelize Authors.
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without

+ 23 - 60
README.md

@@ -6,14 +6,15 @@
     <a href="https://goreportcard.com/report/github.com/360EntSecGroup-Skylar/excelize"><img src="https://goreportcard.com/badge/github.com/360EntSecGroup-Skylar/excelize" alt="Go Report Card"></a>
     <a href="https://pkg.go.dev/github.com/360EntSecGroup-Skylar/excelize/v2?tab=doc"><img src="https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white" alt="go.dev"></a>
     <a href="https://opensource.org/licenses/BSD-3-Clause"><img src="https://img.shields.io/badge/license-bsd-orange.svg" alt="Licenses"></a>
-    <a href="https://www.paypal.com/paypalme/xuri"><img src="https://img.shields.io/badge/Donate-PayPal-green.svg" alt="Donate"></a>
+    <a href="https://www.paypal.me/xuri"><img src="https://img.shields.io/badge/Donate-PayPal-green.svg" alt="Donate"></a>
 </p>
 
 # Excelize
 
 ## Introduction
 
-Excelize is a library written in pure Go providing a set of functions that allow you to write to and read from XLSX / XLSM / XLTM / XLTX files. Supports reading and writing spreadsheet documents generated by Microsoft Excel&trade; 2007 and later. Supports complex components by high compatibility, and provided streaming API for generating or reading data from a worksheet with huge amounts of data. This library needs Go version 1.15 or later. The full API docs can be seen using go's built-in documentation tool, or online at [go.dev](https://pkg.go.dev/github.com/360EntSecGroup-Skylar/excelize/v2?tab=doc) and [docs reference](https://xuri.me/excelize/).
+Excelize is a library written in pure Go providing a set of functions that allow you to write to and read from XLSX files. Supports reading and writing XLSX file generated by Microsoft Excel&trade; 2007 and later.
+Supports saving a file without losing original charts of XLSX. This library needs Go version 1.10 or later. The full API docs can be seen using go's built-in documentation tool, or online at [go.dev](https://pkg.go.dev/github.com/360EntSecGroup-Skylar/excelize/v2?tab=doc) and [docs reference](https://xuri.me/excelize/).
 
 ## Basic Usage
 
@@ -23,15 +24,9 @@ Excelize is a library written in pure Go providing a set of functions that allow
 go get github.com/360EntSecGroup-Skylar/excelize
 ```
 
-- If your packages are managed using [Go Modules](https://blog.golang.org/using-go-modules), please install with following command.
+### Create XLSX file
 
-```bash
-go get github.com/360EntSecGroup-Skylar/excelize/v2
-```
-
-### Create spreadsheet
-
-Here is a minimal example usage that will create spreadsheet file.
+Here is a minimal example usage that will create XLSX file.
 
 ```go
 package main
@@ -39,7 +34,7 @@ package main
 import (
     "fmt"
 
-    "github.com/360EntSecGroup-Skylar/excelize/v2"
+    "github.com/360EntSecGroup-Skylar/excelize"
 )
 
 func main() {
@@ -51,16 +46,16 @@ func main() {
     f.SetCellValue("Sheet1", "B2", 100)
     // Set active sheet of the workbook.
     f.SetActiveSheet(index)
-    // Save spreadsheet by the given path.
+    // Save xlsx file by the given path.
     if err := f.SaveAs("Book1.xlsx"); err != nil {
         fmt.Println(err)
     }
 }
 ```
 
-### Reading spreadsheet
+### Reading XLSX file
 
-The following constitutes the bare to read a spreadsheet document.
+The following constitutes the bare to read a XLSX document.
 
 ```go
 package main
@@ -68,7 +63,7 @@ package main
 import (
     "fmt"
 
-    "github.com/360EntSecGroup-Skylar/excelize/v2"
+    "github.com/360EntSecGroup-Skylar/excelize"
 )
 
 func main() {
@@ -95,9 +90,9 @@ func main() {
 }
 ```
 
-### Add chart to spreadsheet file
+### Add chart to XLSX file
 
-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.
+With Excelize chart generation and management is as easy as a few lines of code. You can build charts based off data in your worksheet or generate charts without any data in your worksheet at all.
 
 <p align="center"><img width="650" src="./test/images/chart.png" alt="Excelize"></p>
 
@@ -107,15 +102,12 @@ package main
 import (
     "fmt"
 
-    "github.com/360EntSecGroup-Skylar/excelize/v2"
+    "github.com/360EntSecGroup-Skylar/excelize"
 )
 
 func main() {
-    categories := map[string]string{
-        "A2": "Small", "A3": "Normal", "A4": "Large",
-        "B1": "Apple", "C1": "Orange", "D1": "Pear"}
-    values := map[string]int{
-        "B2": 2, "C2": 3, "D2": 3, "B3": 5, "C3": 2, "D3": 4, "B4": 6, "C4": 7, "D4": 8}
+    categories := map[string]string{"A2": "Small", "A3": "Normal", "A4": "Large", "B1": "Apple", "C1": "Orange", "D1": "Pear"}
+    values := map[string]int{"B2": 2, "C2": 3, "D2": 3, "B3": 5, "C3": 2, "D3": 4, "B4": 6, "C4": 7, "D4": 8}
     f := excelize.NewFile()
     for k, v := range categories {
         f.SetCellValue("Sheet1", k, v)
@@ -123,40 +115,18 @@ func main() {
     for k, v := range values {
         f.SetCellValue("Sheet1", k, v)
     }
-    if err := f.AddChart("Sheet1", "E1", `{
-        "type": "col3DClustered",
-        "series": [
-        {
-            "name": "Sheet1!$A$2",
-            "categories": "Sheet1!$B$1:$D$1",
-            "values": "Sheet1!$B$2:$D$2"
-        },
-        {
-            "name": "Sheet1!$A$3",
-            "categories": "Sheet1!$B$1:$D$1",
-            "values": "Sheet1!$B$3:$D$3"
-        },
-        {
-            "name": "Sheet1!$A$4",
-            "categories": "Sheet1!$B$1:$D$1",
-            "values": "Sheet1!$B$4:$D$4"
-        }],
-        "title":
-        {
-            "name": "Fruit 3D Clustered Column Chart"
-        }
-    }`); err != nil {
+    if err := f.AddChart("Sheet1", "E1", `{"type":"col3DClustered","series":[{"name":"Sheet1!$A$2","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$2:$D$2"},{"name":"Sheet1!$A$3","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$3:$D$3"},{"name":"Sheet1!$A$4","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$4:$D$4"}],"title":{"name":"Fruit 3D Clustered Column Chart"}}`); err != nil {
         fmt.Println(err)
         return
     }
-    // Save spreadsheet by the given path.
+    // Save xlsx file by the given path.
     if err := f.SaveAs("Book1.xlsx"); err != nil {
         fmt.Println(err)
     }
 }
 ```
 
-### Add picture to spreadsheet file
+### Add picture to XLSX file
 
 ```go
 package main
@@ -167,7 +137,7 @@ import (
     _ "image/jpeg"
     _ "image/png"
 
-    "github.com/360EntSecGroup-Skylar/excelize/v2"
+    "github.com/360EntSecGroup-Skylar/excelize"
 )
 
 func main() {
@@ -181,21 +151,14 @@ func main() {
         fmt.Println(err)
     }
     // Insert a picture to worksheet with scaling.
-    if err := f.AddPicture("Sheet1", "D2", "image.jpg",
-        `{"x_scale": 0.5, "y_scale": 0.5}`); err != nil {
+    if err := f.AddPicture("Sheet1", "D2", "image.jpg", `{"x_scale": 0.5, "y_scale": 0.5}`); err != nil {
         fmt.Println(err)
     }
     // Insert a picture offset in the cell with printing support.
-    if err := f.AddPicture("Sheet1", "H2", "image.gif", `{
-        "x_offset": 15,
-        "y_offset": 10,
-        "print_obj": true,
-        "lock_aspect_ratio": false,
-        "locked": false
-    }`); err != nil {
+    if err := f.AddPicture("Sheet1", "H2", "image.gif", `{"x_offset": 15, "y_offset": 10, "print_obj": true, "lock_aspect_ratio": false, "locked": false}`); err != nil {
         fmt.Println(err)
     }
-    // Save the spreadsheet with the origin path.
+    // Save the xlsx file with the origin path.
     if err = f.Save(); err != nil {
         fmt.Println(err)
     }
@@ -204,7 +167,7 @@ func main() {
 
 ## Contributing
 
-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/).
+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](http://www.ecma-international.org/publications/standards/Ecma-376.htm).
 
 ## Licenses
 

+ 13 - 51
README_zh.md

@@ -6,14 +6,14 @@
     <a href="https://goreportcard.com/report/github.com/360EntSecGroup-Skylar/excelize"><img src="https://goreportcard.com/badge/github.com/360EntSecGroup-Skylar/excelize" alt="Go Report Card"></a>
     <a href="https://pkg.go.dev/github.com/360EntSecGroup-Skylar/excelize/v2?tab=doc"><img src="https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white" alt="go.dev"></a>
     <a href="https://opensource.org/licenses/BSD-3-Clause"><img src="https://img.shields.io/badge/license-bsd-orange.svg" alt="Licenses"></a>
-    <a href="https://www.paypal.com/paypalme/xuri"><img src="https://img.shields.io/badge/Donate-PayPal-green.svg" alt="Donate"></a>
+    <a href="https://www.paypal.me/xuri"><img src="https://img.shields.io/badge/Donate-PayPal-green.svg" alt="Donate"></a>
 </p>
 
 # Excelize
 
 ## 简介
 
-Excelize 是 Go 语言编写的用于操作 Office Excel 文档基础库,基于 ECMA-376,ISO/IEC 29500 国际标准。可以使用它来读取、写入由 Microsoft Excel&trade; 2007 及以上版本创建的电子表格文档。支持 XLSX / XLSM / XLTM / XLTX 等多种文档格式,高度兼容带有样式、图片(表)、透视表、切片器等复杂组件的文档,并提供流式读写 API,用于处理包含大规模数据的工作簿。可应用于各类报表平台、云计算、边缘计算等系统。使用本类库要求使用的 Go 语言为 1.15 或更高版本,完整的 API 使用文档请访问 [go.dev](https://pkg.go.dev/github.com/360EntSecGroup-Skylar/excelize/v2?tab=doc) 或查看 [参考文档](https://xuri.me/excelize/)。
+Excelize 是 Go 语言编写的用于操作 Office Excel 文档类库,基于 ECMA-376 Office OpenXML 标准。可以使用它来读取、写入由 Microsoft Excel&trade; 2007 及以上版本创建的 XLSX 文档。相比较其他的开源类库,Excelize 支持写入原本带有图片(表)、透视表和切片器等复杂样式的文档,还支持向 Excel 文档中插入图片与图表,并且在保存后不会丢失文档原有样式,可以应用于各类报表系统中。使用本类库要求使用的 Go 语言为 1.10 或更高版本,完整的 API 使用文档请访问 [go.dev](https://pkg.go.dev/github.com/360EntSecGroup-Skylar/excelize/v2?tab=doc) 或查看 [参考文档](https://xuri.me/excelize/)。
 
 ## 快速上手
 
@@ -23,12 +23,6 @@ Excelize 是 Go 语言编写的用于操作 Office Excel 文档基础库,基
 go get github.com/360EntSecGroup-Skylar/excelize
 ```
 
-- 如果您使用 [Go Modules](https://blog.golang.org/using-go-modules) 管理软件包,请使用下面的命令来安装最新版本。
-
-```bash
-go get github.com/360EntSecGroup-Skylar/excelize/v2
-```
-
 ### 创建 Excel 文档
 
 下面是一个创建 Excel 文档的简单例子:
@@ -39,7 +33,7 @@ package main
 import (
     "fmt"
 
-    "github.com/360EntSecGroup-Skylar/excelize/v2"
+    "github.com/360EntSecGroup-Skylar/excelize"
 )
 
 func main() {
@@ -68,7 +62,7 @@ package main
 import (
     "fmt"
 
-    "github.com/360EntSecGroup-Skylar/excelize/v2"
+    "github.com/360EntSecGroup-Skylar/excelize"
 )
 
 func main() {
@@ -99,7 +93,7 @@ func main() {
 
 使用 Excelize 生成图表十分简单,仅需几行代码。您可以根据工作表中的已有数据构建图表,或向工作表中添加数据并创建图表。
 
-<p align="center"><img width="650" src="./test/images/chart.png" alt="使用 Excelize 在 Excel 电子表格文档中创建图表"></p>
+<p align="center"><img width="650" src="./test/images/chart.png" alt="Excelize"></p>
 
 ```go
 package main
@@ -107,15 +101,12 @@ package main
 import (
     "fmt"
 
-    "github.com/360EntSecGroup-Skylar/excelize/v2"
+    "github.com/360EntSecGroup-Skylar/excelize"
 )
 
 func main() {
-    categories := map[string]string{
-        "A2": "Small", "A3": "Normal", "A4": "Large",
-        "B1": "Apple", "C1": "Orange", "D1": "Pear"}
-    values := map[string]int{
-        "B2": 2, "C2": 3, "D2": 3, "B3": 5, "C3": 2, "D3": 4, "B4": 6, "C4": 7, "D4": 8}
+    categories := map[string]string{"A2": "Small", "A3": "Normal", "A4": "Large", "B1": "Apple", "C1": "Orange", "D1": "Pear"}
+    values := map[string]int{"B2": 2, "C2": 3, "D2": 3, "B3": 5, "C3": 2, "D3": 4, "B4": 6, "C4": 7, "D4": 8}
     f := excelize.NewFile()
     for k, v := range categories {
         f.SetCellValue("Sheet1", k, v)
@@ -123,29 +114,7 @@ func main() {
     for k, v := range values {
         f.SetCellValue("Sheet1", k, v)
     }
-    if err := f.AddChart("Sheet1", "E1", `{
-        "type": "col3DClustered",
-        "series": [
-        {
-            "name": "Sheet1!$A$2",
-            "categories": "Sheet1!$B$1:$D$1",
-            "values": "Sheet1!$B$2:$D$2"
-        },
-        {
-            "name": "Sheet1!$A$3",
-            "categories": "Sheet1!$B$1:$D$1",
-            "values": "Sheet1!$B$3:$D$3"
-        },
-        {
-            "name": "Sheet1!$A$4",
-            "categories": "Sheet1!$B$1:$D$1",
-            "values": "Sheet1!$B$4:$D$4"
-        }],
-        "title":
-        {
-            "name": "Fruit 3D Clustered Column Chart"
-        }
-    }`); err != nil {
+    if err := f.AddChart("Sheet1", "E1", `{"type":"col3DClustered","series":[{"name":"Sheet1!$A$2","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$2:$D$2"},{"name":"Sheet1!$A$3","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$3:$D$3"},{"name":"Sheet1!$A$4","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$4:$D$4"}],"title":{"name":"Fruit 3D Clustered Column Chart"}}`); err != nil {
         fmt.Println(err)
         return
     }
@@ -167,7 +136,7 @@ import (
     _ "image/jpeg"
     _ "image/png"
 
-    "github.com/360EntSecGroup-Skylar/excelize/v2"
+    "github.com/360EntSecGroup-Skylar/excelize"
 )
 
 func main() {
@@ -181,18 +150,11 @@ func main() {
         fmt.Println(err)
     }
     // 在工作表中插入图片,并设置图片的缩放比例
-    if err := f.AddPicture("Sheet1", "D2", "image.jpg",
-        `{"x_scale": 0.5, "y_scale": 0.5}`); err != nil {
+    if err := f.AddPicture("Sheet1", "D2", "image.jpg", `{"x_scale": 0.5, "y_scale": 0.5}`); err != nil {
         fmt.Println(err)
     }
     // 在工作表中插入图片,并设置图片的打印属性
-    if err := f.AddPicture("Sheet1", "H2", "image.gif", `{
-        "x_offset": 15,
-        "y_offset": 10,
-        "print_obj": true,
-        "lock_aspect_ratio": false,
-        "locked": false
-    }`); err != nil {
+    if err := f.AddPicture("Sheet1", "H2", "image.gif", `{"x_offset": 15, "y_offset": 10, "print_obj": true, "lock_aspect_ratio": false, "locked": false}`); err != nil {
         fmt.Println(err)
     }
     // 保存文件
@@ -204,7 +166,7 @@ func main() {
 
 ## 社区合作
 
-欢迎您为此项目贡献代码,提出建议或问题、修复 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/)。
+欢迎您为此项目贡献代码,提出建议或问题、修复 Bug 以及参与讨论对新功能的想法。 XML 符合标准: [part 1 of the 5th edition of the ECMA-376 Standard for Office Open XML](http://www.ecma-international.org/publications/standards/Ecma-376.htm)。
 
 ## 开源许可
 

+ 58 - 61
adjust.go

@@ -1,17 +1,16 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
+// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
 // this source code is governed by a BSD-style license that can be found in
 // the LICENSE file.
 //
 // Package excelize providing a set of functions that allow you to write to
-// and read from XLSX / XLSM / XLTM files. Supports reading and writing
-// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
-// complex components by high compatibility, and provided streaming API for
-// generating or reading data from a worksheet with huge amounts of data. This
-// library needs Go version 1.15 or later.
+// and read from XLSX files. Support reads and writes XLSX file generated by
+// Microsoft Excel™ 2007 and later. Support save file without losing original
+// charts of XLSX. This library needs Go version 1.10 or later.
 
 package excelize
 
 import (
+	"errors"
 	"strings"
 )
 
@@ -34,31 +33,30 @@ const (
 // TODO: adjustPageBreaks, adjustComments, adjustDataValidations, adjustProtectedCells
 //
 func (f *File) adjustHelper(sheet string, dir adjustDirection, num, offset int) error {
-	ws, err := f.workSheetReader(sheet)
+	xlsx, err := f.workSheetReader(sheet)
 	if err != nil {
 		return err
 	}
-	sheetID := f.getSheetID(sheet)
 	if dir == rows {
-		f.adjustRowDimensions(ws, num, offset)
+		f.adjustRowDimensions(xlsx, num, offset)
 	} else {
-		f.adjustColDimensions(ws, num, offset)
+		f.adjustColDimensions(xlsx, num, offset)
 	}
-	f.adjustHyperlinks(ws, sheet, dir, num, offset)
-	if err = f.adjustMergeCells(ws, dir, num, offset); err != nil {
+	f.adjustHyperlinks(xlsx, sheet, dir, num, offset)
+	if err = f.adjustMergeCells(xlsx, dir, num, offset); err != nil {
 		return err
 	}
-	if err = f.adjustAutoFilter(ws, dir, num, offset); err != nil {
+	if err = f.adjustAutoFilter(xlsx, dir, num, offset); err != nil {
 		return err
 	}
-	if err = f.adjustCalcChain(dir, num, offset, sheetID); err != nil {
+	if err = f.adjustCalcChain(dir, num, offset); err != nil {
 		return err
 	}
-	checkSheet(ws)
-	_ = checkRow(ws)
+	checkSheet(xlsx)
+	_ = checkRow(xlsx)
 
-	if ws.MergeCells != nil && len(ws.MergeCells.Cells) == 0 {
-		ws.MergeCells = nil
+	if xlsx.MergeCells != nil && len(xlsx.MergeCells.Cells) == 0 {
+		xlsx.MergeCells = nil
 	}
 
 	return nil
@@ -66,13 +64,13 @@ func (f *File) adjustHelper(sheet string, dir adjustDirection, num, offset int)
 
 // adjustColDimensions provides a function to update column dimensions when
 // inserting or deleting rows or columns.
-func (f *File) adjustColDimensions(ws *xlsxWorksheet, col, offset int) {
-	for rowIdx := range ws.SheetData.Row {
-		for colIdx, v := range ws.SheetData.Row[rowIdx].C {
+func (f *File) adjustColDimensions(xlsx *xlsxWorksheet, col, offset int) {
+	for rowIdx := range xlsx.SheetData.Row {
+		for colIdx, v := range xlsx.SheetData.Row[rowIdx].C {
 			cellCol, cellRow, _ := CellNameToCoordinates(v.R)
 			if col <= cellCol {
 				if newCol := cellCol + offset; newCol > 0 {
-					ws.SheetData.Row[rowIdx].C[colIdx].R, _ = CoordinatesToCellName(newCol, cellRow)
+					xlsx.SheetData.Row[rowIdx].C[colIdx].R, _ = CoordinatesToCellName(newCol, cellRow)
 				}
 			}
 		}
@@ -81,9 +79,9 @@ func (f *File) adjustColDimensions(ws *xlsxWorksheet, col, offset int) {
 
 // adjustRowDimensions provides a function to update row dimensions when
 // inserting or deleting rows or columns.
-func (f *File) adjustRowDimensions(ws *xlsxWorksheet, row, offset int) {
-	for i := range ws.SheetData.Row {
-		r := &ws.SheetData.Row[i]
+func (f *File) adjustRowDimensions(xlsx *xlsxWorksheet, row, offset int) {
+	for i := range xlsx.SheetData.Row {
+		r := &xlsx.SheetData.Row[i]
 		if newRow := r.R + offset; r.R >= row && newRow > 0 {
 			f.ajustSingleRowDimensions(r, newRow)
 		}
@@ -101,35 +99,37 @@ func (f *File) ajustSingleRowDimensions(r *xlsxRow, num int) {
 
 // adjustHyperlinks provides a function to update hyperlinks when inserting or
 // deleting rows or columns.
-func (f *File) adjustHyperlinks(ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset int) {
+func (f *File) adjustHyperlinks(xlsx *xlsxWorksheet, sheet string, dir adjustDirection, num, offset int) {
 	// short path
-	if ws.Hyperlinks == nil || len(ws.Hyperlinks.Hyperlink) == 0 {
+	if xlsx.Hyperlinks == nil || len(xlsx.Hyperlinks.Hyperlink) == 0 {
 		return
 	}
 
 	// order is important
 	if offset < 0 {
-		for i := len(ws.Hyperlinks.Hyperlink) - 1; i >= 0; i-- {
-			linkData := ws.Hyperlinks.Hyperlink[i]
+		for rowIdx, linkData := range xlsx.Hyperlinks.Hyperlink {
 			colNum, rowNum, _ := CellNameToCoordinates(linkData.Ref)
 
 			if (dir == rows && num == rowNum) || (dir == columns && num == colNum) {
 				f.deleteSheetRelationships(sheet, linkData.RID)
-				if len(ws.Hyperlinks.Hyperlink) > 1 {
-					ws.Hyperlinks.Hyperlink = append(ws.Hyperlinks.Hyperlink[:i],
-						ws.Hyperlinks.Hyperlink[i+1:]...)
+				if len(xlsx.Hyperlinks.Hyperlink) > 1 {
+					xlsx.Hyperlinks.Hyperlink = append(xlsx.Hyperlinks.Hyperlink[:rowIdx],
+						xlsx.Hyperlinks.Hyperlink[rowIdx+1:]...)
 				} else {
-					ws.Hyperlinks = nil
+					xlsx.Hyperlinks = nil
 				}
 			}
 		}
 	}
-	if ws.Hyperlinks == nil {
+
+	if xlsx.Hyperlinks == nil {
 		return
 	}
-	for i := range ws.Hyperlinks.Hyperlink {
-		link := &ws.Hyperlinks.Hyperlink[i] // get reference
+
+	for i := range xlsx.Hyperlinks.Hyperlink {
+		link := &xlsx.Hyperlinks.Hyperlink[i] // get reference
 		colNum, rowNum, _ := CellNameToCoordinates(link.Ref)
+
 		if dir == rows {
 			if rowNum >= num {
 				link.Ref, _ = CoordinatesToCellName(colNum, rowNum+offset)
@@ -144,21 +144,21 @@ func (f *File) adjustHyperlinks(ws *xlsxWorksheet, sheet string, dir adjustDirec
 
 // adjustAutoFilter provides a function to update the auto filter when
 // inserting or deleting rows or columns.
-func (f *File) adjustAutoFilter(ws *xlsxWorksheet, dir adjustDirection, num, offset int) error {
-	if ws.AutoFilter == nil {
+func (f *File) adjustAutoFilter(xlsx *xlsxWorksheet, dir adjustDirection, num, offset int) error {
+	if xlsx.AutoFilter == nil {
 		return nil
 	}
 
-	coordinates, err := f.areaRefToCoordinates(ws.AutoFilter.Ref)
+	coordinates, err := f.areaRefToCoordinates(xlsx.AutoFilter.Ref)
 	if err != nil {
 		return err
 	}
 	x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3]
 
 	if (dir == rows && y1 == num && offset < 0) || (dir == columns && x1 == num && x2 == num) {
-		ws.AutoFilter = nil
-		for rowIdx := range ws.SheetData.Row {
-			rowData := &ws.SheetData.Row[rowIdx]
+		xlsx.AutoFilter = nil
+		for rowIdx := range xlsx.SheetData.Row {
+			rowData := &xlsx.SheetData.Row[rowIdx]
 			if rowData.R > y1 && rowData.R <= y2 {
 				rowData.Hidden = false
 			}
@@ -169,7 +169,7 @@ func (f *File) adjustAutoFilter(ws *xlsxWorksheet, dir adjustDirection, num, off
 	coordinates = f.adjustAutoFilterHelper(dir, coordinates, num, offset)
 	x1, y1, x2, y2 = coordinates[0], coordinates[1], coordinates[2], coordinates[3]
 
-	if ws.AutoFilter.Ref, err = f.coordinatesToAreaRef([]int{x1, y1, x2, y2}); err != nil {
+	if xlsx.AutoFilter.Ref, err = f.coordinatesToAreaRef([]int{x1, y1, x2, y2}); err != nil {
 		return err
 	}
 	return nil
@@ -197,7 +197,7 @@ func (f *File) adjustAutoFilterHelper(dir adjustDirection, coordinates []int, nu
 // areaRefToCoordinates provides a function to convert area reference to a
 // pair of coordinates.
 func (f *File) areaRefToCoordinates(ref string) ([]int, error) {
-	rng := strings.Split(strings.Replace(ref, "$", "", -1), ":")
+	rng := strings.Split(ref, ":")
 	return areaRangeToCoordinates(rng[0], rng[1])
 }
 
@@ -218,7 +218,7 @@ func areaRangeToCoordinates(firstCell, lastCell string) ([]int, error) {
 // correct C1:B3 to B1:C3.
 func sortCoordinates(coordinates []int) error {
 	if len(coordinates) != 4 {
-		return ErrCoordinates
+		return errors.New("coordinates length must be 4")
 	}
 	if coordinates[2] < coordinates[0] {
 		coordinates[2], coordinates[0] = coordinates[0], coordinates[2]
@@ -233,7 +233,7 @@ func sortCoordinates(coordinates []int) error {
 // to area reference.
 func (f *File) coordinatesToAreaRef(coordinates []int) (string, error) {
 	if len(coordinates) != 4 {
-		return "", ErrCoordinates
+		return "", errors.New("coordinates length must be 4")
 	}
 	firstCell, err := CoordinatesToCellName(coordinates[0], coordinates[1])
 	if err != nil {
@@ -248,13 +248,13 @@ func (f *File) coordinatesToAreaRef(coordinates []int) (string, error) {
 
 // adjustMergeCells provides a function to update merged cells when inserting
 // or deleting rows or columns.
-func (f *File) adjustMergeCells(ws *xlsxWorksheet, dir adjustDirection, num, offset int) error {
-	if ws.MergeCells == nil {
+func (f *File) adjustMergeCells(xlsx *xlsxWorksheet, dir adjustDirection, num, offset int) error {
+	if xlsx.MergeCells == nil {
 		return nil
 	}
 
-	for i := 0; i < len(ws.MergeCells.Cells); i++ {
-		areaData := ws.MergeCells.Cells[i]
+	for i := 0; i < len(xlsx.MergeCells.Cells); i++ {
+		areaData := xlsx.MergeCells.Cells[i]
 		coordinates, err := f.areaRefToCoordinates(areaData.Ref)
 		if err != nil {
 			return err
@@ -262,21 +262,21 @@ func (f *File) adjustMergeCells(ws *xlsxWorksheet, dir adjustDirection, num, off
 		x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3]
 		if dir == rows {
 			if y1 == num && y2 == num && offset < 0 {
-				f.deleteMergeCell(ws, i)
+				f.deleteMergeCell(xlsx, i)
 				i--
 			}
 			y1 = f.adjustMergeCellsHelper(y1, num, offset)
 			y2 = f.adjustMergeCellsHelper(y2, num, offset)
 		} else {
 			if x1 == num && x2 == num && offset < 0 {
-				f.deleteMergeCell(ws, i)
+				f.deleteMergeCell(xlsx, i)
 				i--
 			}
 			x1 = f.adjustMergeCellsHelper(x1, num, offset)
 			x2 = f.adjustMergeCellsHelper(x2, num, offset)
 		}
 		if x1 == x2 && y1 == y2 {
-			f.deleteMergeCell(ws, i)
+			f.deleteMergeCell(xlsx, i)
 			i--
 		}
 		if areaData.Ref, err = f.coordinatesToAreaRef([]int{x1, y1, x2, y2}); err != nil {
@@ -301,23 +301,20 @@ func (f *File) adjustMergeCellsHelper(pivot, num, offset int) int {
 }
 
 // deleteMergeCell provides a function to delete merged cell by given index.
-func (f *File) deleteMergeCell(ws *xlsxWorksheet, idx int) {
-	if len(ws.MergeCells.Cells) > idx {
-		ws.MergeCells.Cells = append(ws.MergeCells.Cells[:idx], ws.MergeCells.Cells[idx+1:]...)
-		ws.MergeCells.Count = len(ws.MergeCells.Cells)
+func (f *File) deleteMergeCell(sheet *xlsxWorksheet, idx int) {
+	if len(sheet.MergeCells.Cells) > idx {
+		sheet.MergeCells.Cells = append(sheet.MergeCells.Cells[:idx], sheet.MergeCells.Cells[idx+1:]...)
+		sheet.MergeCells.Count = len(sheet.MergeCells.Cells)
 	}
 }
 
 // adjustCalcChain provides a function to update the calculation chain when
 // inserting or deleting rows or columns.
-func (f *File) adjustCalcChain(dir adjustDirection, num, offset, sheetID int) error {
+func (f *File) adjustCalcChain(dir adjustDirection, num, offset int) error {
 	if f.CalcChain == nil {
 		return nil
 	}
 	for index, c := range f.CalcChain.C {
-		if c.I != sheetID {
-			continue
-		}
 		colNum, rowNum, err := CellNameToCoordinates(c.R)
 		if err != nil {
 			return err

+ 4 - 12
adjust_test.go

@@ -49,14 +49,6 @@ func TestAdjustMergeCells(t *testing.T) {
 
 func TestAdjustAutoFilter(t *testing.T) {
 	f := NewFile()
-	assert.NoError(t, f.adjustAutoFilter(&xlsxWorksheet{
-		SheetData: xlsxSheetData{
-			Row: []xlsxRow{{Hidden: true, R: 2}},
-		},
-		AutoFilter: &xlsxAutoFilter{
-			Ref: "A1:A3",
-		},
-	}, rows, 1, -1))
 	// testing adjustAutoFilter with illegal cell coordinates.
 	assert.EqualError(t, f.adjustAutoFilter(&xlsxWorksheet{
 		AutoFilter: &xlsxAutoFilter{
@@ -98,13 +90,13 @@ func TestAdjustCalcChain(t *testing.T) {
 	f := NewFile()
 	f.CalcChain = &xlsxCalcChain{
 		C: []xlsxCalcChainC{
-			{R: "B2", I: 2}, {R: "B2", I: 1},
+			{R: "B2"},
 		},
 	}
 	assert.NoError(t, f.InsertCol("Sheet1", "A"))
 	assert.NoError(t, f.InsertRow("Sheet1", 1))
 
-	f.CalcChain.C[1].R = "invalid coordinates"
+	f.CalcChain.C[0].R = "invalid coordinates"
 	assert.EqualError(t, f.InsertCol("Sheet1", "A"), `cannot convert cell "invalid coordinates" to coordinates: invalid cell name "invalid coordinates"`)
 	f.CalcChain = nil
 	assert.NoError(t, f.InsertCol("Sheet1", "A"))
@@ -113,7 +105,7 @@ func TestAdjustCalcChain(t *testing.T) {
 func TestCoordinatesToAreaRef(t *testing.T) {
 	f := NewFile()
 	_, err := f.coordinatesToAreaRef([]int{})
-	assert.EqualError(t, err, ErrCoordinates.Error())
+	assert.EqualError(t, err, "coordinates length must be 4")
 	_, err = f.coordinatesToAreaRef([]int{1, -1, 1, 1})
 	assert.EqualError(t, err, "invalid cell coordinates [1, -1]")
 	_, err = f.coordinatesToAreaRef([]int{1, 1, 1, -1})
@@ -124,5 +116,5 @@ func TestCoordinatesToAreaRef(t *testing.T) {
 }
 
 func TestSortCoordinates(t *testing.T) {
-	assert.EqualError(t, sortCoordinates(make([]int, 3)), ErrCoordinates.Error())
+	assert.EqualError(t, sortCoordinates(make([]int, 3)), "coordinates length must be 4")
 }

+ 0 - 8067
calc.go

@@ -1,8067 +0,0 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
-// this source code is governed by a BSD-style license that can be found in
-// the LICENSE file.
-//
-// Package excelize providing a set of functions that allow you to write to
-// and read from XLSX / XLSM / XLTM files. Supports reading and writing
-// spreadsheet documents generated by Microsoft 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.15 or later.
-
-package excelize
-
-import (
-	"bytes"
-	"container/list"
-	"errors"
-	"fmt"
-	"math"
-	"math/cmplx"
-	"math/rand"
-	"net/url"
-	"reflect"
-	"regexp"
-	"sort"
-	"strconv"
-	"strings"
-	"time"
-	"unicode"
-	"unsafe"
-
-	"github.com/xuri/efp"
-	"golang.org/x/text/language"
-	"golang.org/x/text/message"
-)
-
-// Excel formula errors
-const (
-	formulaErrorDIV         = "#DIV/0!"
-	formulaErrorNAME        = "#NAME?"
-	formulaErrorNA          = "#N/A"
-	formulaErrorNUM         = "#NUM!"
-	formulaErrorVALUE       = "#VALUE!"
-	formulaErrorREF         = "#REF!"
-	formulaErrorNULL        = "#NULL"
-	formulaErrorSPILL       = "#SPILL!"
-	formulaErrorCALC        = "#CALC!"
-	formulaErrorGETTINGDATA = "#GETTING_DATA"
-)
-
-// Numeric precision correct numeric values as legacy Excel application
-// https://en.wikipedia.org/wiki/Numeric_precision_in_Microsoft_Excel In the
-// top figure the fraction 1/9000 in Excel is displayed. Although this number
-// has a decimal representation that is an infinite string of ones, Excel
-// displays only the leading 15 figures. In the second line, the number one
-// is added to the fraction, and again Excel displays only 15 figures.
-const numericPrecision = 1000000000000000
-const maxFinancialIterations = 128
-const financialPercision = 1.0e-08
-
-// cellRef defines the structure of a cell reference.
-type cellRef struct {
-	Col   int
-	Row   int
-	Sheet string
-}
-
-// cellRef defines the structure of a cell range.
-type cellRange struct {
-	From cellRef
-	To   cellRef
-}
-
-// formula criteria condition enumeration.
-const (
-	_ byte = iota
-	criteriaEq
-	criteriaLe
-	criteriaGe
-	criteriaL
-	criteriaG
-	criteriaBeg
-	criteriaEnd
-	criteriaErr
-)
-
-// formulaCriteria defined formula criteria parser result.
-type formulaCriteria struct {
-	Type      byte
-	Condition string
-}
-
-// ArgType is the type if formula argument type.
-type ArgType byte
-
-// Formula argument types enumeration.
-const (
-	ArgUnknown ArgType = iota
-	ArgNumber
-	ArgString
-	ArgList
-	ArgMatrix
-	ArgError
-	ArgEmpty
-)
-
-// formulaArg is the argument of a formula or function.
-type formulaArg struct {
-	SheetName            string
-	Number               float64
-	String               string
-	List                 []formulaArg
-	Matrix               [][]formulaArg
-	Boolean              bool
-	Error                string
-	Type                 ArgType
-	cellRefs, cellRanges *list.List
-}
-
-// Value returns a string data type of the formula argument.
-func (fa formulaArg) Value() (value string) {
-	switch fa.Type {
-	case ArgNumber:
-		if fa.Boolean {
-			if fa.Number == 0 {
-				return "FALSE"
-			}
-			return "TRUE"
-		}
-		return fmt.Sprintf("%g", fa.Number)
-	case ArgString:
-		return fa.String
-	case ArgError:
-		return fa.Error
-	}
-	return
-}
-
-// ToNumber returns a formula argument with number data type.
-func (fa formulaArg) ToNumber() formulaArg {
-	var n float64
-	var err error
-	switch fa.Type {
-	case ArgString:
-		n, err = strconv.ParseFloat(fa.String, 64)
-		if err != nil {
-			return newErrorFormulaArg(formulaErrorVALUE, err.Error())
-		}
-	case ArgNumber:
-		n = fa.Number
-	}
-	return newNumberFormulaArg(n)
-}
-
-// ToBool returns a formula argument with boolean data type.
-func (fa formulaArg) ToBool() formulaArg {
-	var b bool
-	var err error
-	switch fa.Type {
-	case ArgString:
-		b, err = strconv.ParseBool(fa.String)
-		if err != nil {
-			return newErrorFormulaArg(formulaErrorVALUE, err.Error())
-		}
-	case ArgNumber:
-		if fa.Boolean && fa.Number == 1 {
-			b = true
-		}
-	}
-	return newBoolFormulaArg(b)
-}
-
-// ToList returns a formula argument with array data type.
-func (fa formulaArg) ToList() []formulaArg {
-	switch fa.Type {
-	case ArgMatrix:
-		list := []formulaArg{}
-		for _, row := range fa.Matrix {
-			list = append(list, row...)
-		}
-		return list
-	case ArgList:
-		return fa.List
-	case ArgNumber, ArgString, ArgError, ArgUnknown:
-		return []formulaArg{fa}
-	}
-	return nil
-}
-
-// formulaFuncs is the type of the formula functions.
-type formulaFuncs struct {
-	f           *File
-	sheet, cell string
-}
-
-// tokenPriority defined basic arithmetic operator priority.
-var tokenPriority = map[string]int{
-	"^":  5,
-	"*":  4,
-	"/":  4,
-	"+":  3,
-	"-":  3,
-	"=":  2,
-	"<>": 2,
-	"<":  2,
-	"<=": 2,
-	">":  2,
-	">=": 2,
-	"&":  1,
-}
-
-// CalcCellValue provides a function to get calculated cell value. This
-// feature is currently in working processing. Array formula, table formula
-// and some other formulas are not supported currently.
-//
-// Supported formula functions:
-//
-//    ABS
-//    ACOS
-//    ACOSH
-//    ACOT
-//    ACOTH
-//    AND
-//    ARABIC
-//    ASIN
-//    ASINH
-//    ATAN
-//    ATAN2
-//    ATANH
-//    AVERAGE
-//    AVERAGEA
-//    BASE
-//    BESSELI
-//    BESSELJ
-//    BESSELK
-//    BESSELY
-//    BIN2DEC
-//    BIN2HEX
-//    BIN2OCT
-//    BITAND
-//    BITLSHIFT
-//    BITOR
-//    BITRSHIFT
-//    BITXOR
-//    CEILING
-//    CEILING.MATH
-//    CEILING.PRECISE
-//    CHAR
-//    CHOOSE
-//    CLEAN
-//    CODE
-//    COLUMN
-//    COLUMNS
-//    COMBIN
-//    COMBINA
-//    COMPLEX
-//    CONCAT
-//    CONCATENATE
-//    COS
-//    COSH
-//    COT
-//    COTH
-//    COUNT
-//    COUNTA
-//    COUNTBLANK
-//    CSC
-//    CSCH
-//    CUMIPMT
-//    CUMPRINC
-//    DATE
-//    DATEDIF
-//    DB
-//    DDB
-//    DEC2BIN
-//    DEC2HEX
-//    DEC2OCT
-//    DECIMAL
-//    DEGREES
-//    DOLLARDE
-//    DOLLARFR
-//    EFFECT
-//    ENCODEURL
-//    EVEN
-//    EXACT
-//    EXP
-//    FACT
-//    FACTDOUBLE
-//    FALSE
-//    FIND
-//    FINDB
-//    FISHER
-//    FISHERINV
-//    FIXED
-//    FLOOR
-//    FLOOR.MATH
-//    FLOOR.PRECISE
-//    FV
-//    FVSCHEDULE
-//    GAMMA
-//    GAMMALN
-//    GCD
-//    HARMEAN
-//    HEX2BIN
-//    HEX2DEC
-//    HEX2OCT
-//    HLOOKUP
-//    IF
-//    IFERROR
-//    IMABS
-//    IMAGINARY
-//    IMARGUMENT
-//    IMCONJUGATE
-//    IMCOS
-//    IMCOSH
-//    IMCOT
-//    IMCSC
-//    IMCSCH
-//    IMDIV
-//    IMEXP
-//    IMLN
-//    IMLOG10
-//    IMLOG2
-//    IMPOWER
-//    IMPRODUCT
-//    IMREAL
-//    IMSEC
-//    IMSECH
-//    IMSIN
-//    IMSINH
-//    IMSQRT
-//    IMSUB
-//    IMSUM
-//    IMTAN
-//    INT
-//    IPMT
-//    IRR
-//    ISBLANK
-//    ISERR
-//    ISERROR
-//    ISEVEN
-//    ISNA
-//    ISNONTEXT
-//    ISNUMBER
-//    ISODD
-//    ISTEXT
-//    ISO.CEILING
-//    ISPMT
-//    KURT
-//    LARGE
-//    LCM
-//    LEFT
-//    LEFTB
-//    LEN
-//    LENB
-//    LN
-//    LOG
-//    LOG10
-//    LOOKUP
-//    LOWER
-//    MAX
-//    MDETERM
-//    MEDIAN
-//    MID
-//    MIDB
-//    MIN
-//    MINA
-//    MIRR
-//    MOD
-//    MROUND
-//    MULTINOMIAL
-//    MUNIT
-//    N
-//    NA
-//    NOMINAL
-//    NORM.DIST
-//    NORMDIST
-//    NORM.INV
-//    NORMINV
-//    NORM.S.DIST
-//    NORMSDIST
-//    NORM.S.INV
-//    NORMSINV
-//    NOT
-//    NOW
-//    NPER
-//    NPV
-//    OCT2BIN
-//    OCT2DEC
-//    OCT2HEX
-//    ODD
-//    OR
-//    PDURATION
-//    PERCENTILE.INC
-//    PERCENTILE
-//    PERMUT
-//    PERMUTATIONA
-//    PI
-//    PMT
-//    POISSON.DIST
-//    POISSON
-//    POWER
-//    PPMT
-//    PRODUCT
-//    PROPER
-//    QUARTILE
-//    QUARTILE.INC
-//    QUOTIENT
-//    RADIANS
-//    RAND
-//    RANDBETWEEN
-//    REPLACE
-//    REPLACEB
-//    REPT
-//    RIGHT
-//    RIGHTB
-//    ROMAN
-//    ROUND
-//    ROUNDDOWN
-//    ROUNDUP
-//    ROW
-//    ROWS
-//    SEC
-//    SECH
-//    SHEET
-//    SIGN
-//    SIN
-//    SINH
-//    SKEW
-//    SMALL
-//    SQRT
-//    SQRTPI
-//    STDEV
-//    STDEV.S
-//    STDEVA
-//    SUBSTITUTE
-//    SUM
-//    SUMIF
-//    SUMSQ
-//    T
-//    TAN
-//    TANH
-//    TODAY
-//    TRIM
-//    TRUE
-//    TRUNC
-//    UNICHAR
-//    UNICODE
-//    UPPER
-//    VAR.P
-//    VARP
-//    VLOOKUP
-//
-func (f *File) CalcCellValue(sheet, cell string) (result string, err error) {
-	var (
-		formula string
-		token   efp.Token
-	)
-	if formula, err = f.GetCellFormula(sheet, cell); err != nil {
-		return
-	}
-	ps := efp.ExcelParser()
-	tokens := ps.Parse(formula)
-	if tokens == nil {
-		return
-	}
-	if token, err = f.evalInfixExp(sheet, cell, tokens); err != nil {
-		return
-	}
-	result = token.TValue
-	isNum, precision := isNumeric(result)
-	if isNum && precision > 15 {
-		num, _ := roundPrecision(result)
-		result = strings.ToUpper(num)
-	}
-	return
-}
-
-// getPriority calculate arithmetic operator priority.
-func getPriority(token efp.Token) (pri int) {
-	pri = tokenPriority[token.TValue]
-	if token.TValue == "-" && token.TType == efp.TokenTypeOperatorPrefix {
-		pri = 6
-	}
-	if isBeginParenthesesToken(token) { // (
-		pri = 0
-	}
-	return
-}
-
-// newNumberFormulaArg constructs a number formula argument.
-func newNumberFormulaArg(n float64) formulaArg {
-	if math.IsNaN(n) {
-		return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
-	}
-	return formulaArg{Type: ArgNumber, Number: n}
-}
-
-// newStringFormulaArg constructs a string formula argument.
-func newStringFormulaArg(s string) formulaArg {
-	return formulaArg{Type: ArgString, String: s}
-}
-
-// newMatrixFormulaArg constructs a matrix formula argument.
-func newMatrixFormulaArg(m [][]formulaArg) formulaArg {
-	return formulaArg{Type: ArgMatrix, Matrix: m}
-}
-
-// newListFormulaArg create a list formula argument.
-func newListFormulaArg(l []formulaArg) formulaArg {
-	return formulaArg{Type: ArgList, List: l}
-}
-
-// newBoolFormulaArg constructs a boolean formula argument.
-func newBoolFormulaArg(b bool) formulaArg {
-	var n float64
-	if b {
-		n = 1
-	}
-	return formulaArg{Type: ArgNumber, Number: n, Boolean: true}
-}
-
-// newErrorFormulaArg create an error formula argument of a given type with a
-// specified error message.
-func newErrorFormulaArg(formulaError, msg string) formulaArg {
-	return formulaArg{Type: ArgError, String: formulaError, Error: msg}
-}
-
-// newEmptyFormulaArg create an empty formula argument.
-func newEmptyFormulaArg() formulaArg {
-	return formulaArg{Type: ArgEmpty}
-}
-
-// evalInfixExp evaluate syntax analysis by given infix expression after
-// lexical analysis. Evaluate an infix expression containing formulas by
-// stacks:
-//
-//    opd  - Operand
-//    opt  - Operator
-//    opf  - Operation formula
-//    opfd - Operand of the operation formula
-//    opft - Operator of the operation formula
-//    args - Arguments list of the operation formula
-//
-// TODO: handle subtypes: Nothing, Text, Logical, Error, Concatenation, Intersection, Union
-//
-func (f *File) evalInfixExp(sheet, cell string, tokens []efp.Token) (efp.Token, error) {
-	var err error
-	opdStack, optStack, opfStack, opfdStack, opftStack, argsStack := NewStack(), NewStack(), NewStack(), NewStack(), NewStack(), NewStack()
-	for i := 0; i < len(tokens); i++ {
-		token := tokens[i]
-
-		// out of function stack
-		if opfStack.Len() == 0 {
-			if err = f.parseToken(sheet, token, opdStack, optStack); err != nil {
-				return efp.Token{}, err
-			}
-		}
-
-		// function start
-		if isFunctionStartToken(token) {
-			opfStack.Push(token)
-			argsStack.Push(list.New().Init())
-			continue
-		}
-
-		// in function stack, walk 2 token at once
-		if opfStack.Len() > 0 {
-			var nextToken efp.Token
-			if i+1 < len(tokens) {
-				nextToken = tokens[i+1]
-			}
-
-			// current token is args or range, skip next token, order required: parse reference first
-			if token.TSubType == efp.TokenSubTypeRange {
-				if !opftStack.Empty() {
-					// parse reference: must reference at here
-					result, err := f.parseReference(sheet, token.TValue)
-					if err != nil {
-						return efp.Token{TValue: formulaErrorNAME}, err
-					}
-					if result.Type != ArgString {
-						return efp.Token{}, errors.New(formulaErrorVALUE)
-					}
-					opfdStack.Push(efp.Token{
-						TType:    efp.TokenTypeOperand,
-						TSubType: efp.TokenSubTypeNumber,
-						TValue:   result.String,
-					})
-					continue
-				}
-				if nextToken.TType == efp.TokenTypeArgument || nextToken.TType == efp.TokenTypeFunction {
-					// parse reference: reference or range at here
-					result, err := f.parseReference(sheet, token.TValue)
-					if err != nil {
-						return efp.Token{TValue: formulaErrorNAME}, err
-					}
-					if result.Type == ArgUnknown {
-						return efp.Token{}, errors.New(formulaErrorVALUE)
-					}
-					argsStack.Peek().(*list.List).PushBack(result)
-					continue
-				}
-			}
-
-			// check current token is opft
-			if err = f.parseToken(sheet, token, opfdStack, opftStack); err != nil {
-				return efp.Token{}, err
-			}
-
-			// current token is arg
-			if token.TType == efp.TokenTypeArgument {
-				for !opftStack.Empty() {
-					// calculate trigger
-					topOpt := opftStack.Peek().(efp.Token)
-					if err := calculate(opfdStack, topOpt); err != nil {
-						argsStack.Peek().(*list.List).PushFront(newErrorFormulaArg(formulaErrorVALUE, err.Error()))
-					}
-					opftStack.Pop()
-				}
-				if !opfdStack.Empty() {
-					argsStack.Peek().(*list.List).PushBack(newStringFormulaArg(opfdStack.Pop().(efp.Token).TValue))
-				}
-				continue
-			}
-
-			// current token is logical
-			if token.TType == efp.OperatorsInfix && token.TSubType == efp.TokenSubTypeLogical {
-			}
-			if token.TType == efp.TokenTypeOperand && token.TSubType == efp.TokenSubTypeLogical {
-				argsStack.Peek().(*list.List).PushBack(newStringFormulaArg(token.TValue))
-			}
-
-			// current token is text
-			if token.TType == efp.TokenTypeOperand && token.TSubType == efp.TokenSubTypeText {
-				argsStack.Peek().(*list.List).PushBack(newStringFormulaArg(token.TValue))
-			}
-			if err = f.evalInfixExpFunc(sheet, cell, token, nextToken, opfStack, opdStack, opftStack, opfdStack, argsStack); err != nil {
-				return efp.Token{}, err
-			}
-		}
-	}
-	for optStack.Len() != 0 {
-		topOpt := optStack.Peek().(efp.Token)
-		if err = calculate(opdStack, topOpt); err != nil {
-			return efp.Token{}, err
-		}
-		optStack.Pop()
-	}
-	if opdStack.Len() == 0 {
-		return efp.Token{}, ErrInvalidFormula
-	}
-	return opdStack.Peek().(efp.Token), err
-}
-
-// evalInfixExpFunc evaluate formula function in the infix expression.
-func (f *File) evalInfixExpFunc(sheet, cell string, token, nextToken efp.Token, opfStack, opdStack, opftStack, opfdStack, argsStack *Stack) error {
-	if !isFunctionStopToken(token) {
-		return nil
-	}
-	// current token is function stop
-	for !opftStack.Empty() {
-		// calculate trigger
-		topOpt := opftStack.Peek().(efp.Token)
-		if err := calculate(opfdStack, topOpt); err != nil {
-			return err
-		}
-		opftStack.Pop()
-	}
-
-	// push opfd to args
-	if opfdStack.Len() > 0 {
-		argsStack.Peek().(*list.List).PushBack(newStringFormulaArg(opfdStack.Pop().(efp.Token).TValue))
-	}
-	// call formula function to evaluate
-	arg := callFuncByName(&formulaFuncs{f: f, sheet: sheet, cell: cell}, strings.NewReplacer(
-		"_xlfn.", "", ".", "dot").Replace(opfStack.Peek().(efp.Token).TValue),
-		[]reflect.Value{reflect.ValueOf(argsStack.Peek().(*list.List))})
-	if arg.Type == ArgError && opfStack.Len() == 1 {
-		return errors.New(arg.Value())
-	}
-	argsStack.Pop()
-	opfStack.Pop()
-	if opfStack.Len() > 0 { // still in function stack
-		if nextToken.TType == efp.TokenTypeOperatorInfix {
-			// mathematics calculate in formula function
-			opfdStack.Push(efp.Token{TValue: arg.Value(), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
-		} else {
-			argsStack.Peek().(*list.List).PushBack(arg)
-		}
-	} else {
-		opdStack.Push(efp.Token{TValue: arg.Value(), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
-	}
-	return nil
-}
-
-// calcPow evaluate exponentiation arithmetic operations.
-func calcPow(rOpd, lOpd string, opdStack *Stack) error {
-	lOpdVal, err := strconv.ParseFloat(lOpd, 64)
-	if err != nil {
-		return err
-	}
-	rOpdVal, err := strconv.ParseFloat(rOpd, 64)
-	if err != nil {
-		return err
-	}
-	result := math.Pow(lOpdVal, rOpdVal)
-	opdStack.Push(efp.Token{TValue: fmt.Sprintf("%g", result), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
-	return nil
-}
-
-// calcEq evaluate equal arithmetic operations.
-func calcEq(rOpd, lOpd string, opdStack *Stack) error {
-	opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(rOpd == lOpd)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
-	return nil
-}
-
-// calcNEq evaluate not equal arithmetic operations.
-func calcNEq(rOpd, lOpd string, opdStack *Stack) error {
-	opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(rOpd != lOpd)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
-	return nil
-}
-
-// calcL evaluate less than arithmetic operations.
-func calcL(rOpd, lOpd string, opdStack *Stack) error {
-	lOpdVal, err := strconv.ParseFloat(lOpd, 64)
-	if err != nil {
-		return err
-	}
-	rOpdVal, err := strconv.ParseFloat(rOpd, 64)
-	if err != nil {
-		return err
-	}
-	opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(rOpdVal > lOpdVal)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
-	return nil
-}
-
-// calcLe evaluate less than or equal arithmetic operations.
-func calcLe(rOpd, lOpd string, opdStack *Stack) error {
-	lOpdVal, err := strconv.ParseFloat(lOpd, 64)
-	if err != nil {
-		return err
-	}
-	rOpdVal, err := strconv.ParseFloat(rOpd, 64)
-	if err != nil {
-		return err
-	}
-	opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(rOpdVal >= lOpdVal)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
-	return nil
-}
-
-// calcG evaluate greater than or equal arithmetic operations.
-func calcG(rOpd, lOpd string, opdStack *Stack) error {
-	lOpdVal, err := strconv.ParseFloat(lOpd, 64)
-	if err != nil {
-		return err
-	}
-	rOpdVal, err := strconv.ParseFloat(rOpd, 64)
-	if err != nil {
-		return err
-	}
-	opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(rOpdVal < lOpdVal)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
-	return nil
-}
-
-// calcGe evaluate greater than or equal arithmetic operations.
-func calcGe(rOpd, lOpd string, opdStack *Stack) error {
-	lOpdVal, err := strconv.ParseFloat(lOpd, 64)
-	if err != nil {
-		return err
-	}
-	rOpdVal, err := strconv.ParseFloat(rOpd, 64)
-	if err != nil {
-		return err
-	}
-	opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(rOpdVal <= lOpdVal)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
-	return nil
-}
-
-// calcSplice evaluate splice '&' operations.
-func calcSplice(rOpd, lOpd string, opdStack *Stack) error {
-	opdStack.Push(efp.Token{TValue: lOpd + rOpd, TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
-	return nil
-}
-
-// calcAdd evaluate addition arithmetic operations.
-func calcAdd(rOpd, lOpd string, opdStack *Stack) error {
-	lOpdVal, err := strconv.ParseFloat(lOpd, 64)
-	if err != nil {
-		return err
-	}
-	rOpdVal, err := strconv.ParseFloat(rOpd, 64)
-	if err != nil {
-		return err
-	}
-	result := lOpdVal + rOpdVal
-	opdStack.Push(efp.Token{TValue: fmt.Sprintf("%g", result), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
-	return nil
-}
-
-// calcSubtract evaluate subtraction arithmetic operations.
-func calcSubtract(rOpd, lOpd string, opdStack *Stack) error {
-	lOpdVal, err := strconv.ParseFloat(lOpd, 64)
-	if err != nil {
-		return err
-	}
-	rOpdVal, err := strconv.ParseFloat(rOpd, 64)
-	if err != nil {
-		return err
-	}
-	result := lOpdVal - rOpdVal
-	opdStack.Push(efp.Token{TValue: fmt.Sprintf("%g", result), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
-	return nil
-}
-
-// calcMultiply evaluate multiplication arithmetic operations.
-func calcMultiply(rOpd, lOpd string, opdStack *Stack) error {
-	lOpdVal, err := strconv.ParseFloat(lOpd, 64)
-	if err != nil {
-		return err
-	}
-	rOpdVal, err := strconv.ParseFloat(rOpd, 64)
-	if err != nil {
-		return err
-	}
-	result := lOpdVal * rOpdVal
-	opdStack.Push(efp.Token{TValue: fmt.Sprintf("%g", result), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
-	return nil
-}
-
-// calcDiv evaluate division arithmetic operations.
-func calcDiv(rOpd, lOpd string, opdStack *Stack) error {
-	lOpdVal, err := strconv.ParseFloat(lOpd, 64)
-	if err != nil {
-		return err
-	}
-	rOpdVal, err := strconv.ParseFloat(rOpd, 64)
-	if err != nil {
-		return err
-	}
-	result := lOpdVal / rOpdVal
-	if rOpdVal == 0 {
-		return errors.New(formulaErrorDIV)
-	}
-	opdStack.Push(efp.Token{TValue: fmt.Sprintf("%g", result), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
-	return nil
-}
-
-// calculate evaluate basic arithmetic operations.
-func calculate(opdStack *Stack, opt efp.Token) error {
-	if opt.TValue == "-" && opt.TType == efp.TokenTypeOperatorPrefix {
-		if opdStack.Len() < 1 {
-			return ErrInvalidFormula
-		}
-		opd := opdStack.Pop().(efp.Token)
-		opdVal, err := strconv.ParseFloat(opd.TValue, 64)
-		if err != nil {
-			return err
-		}
-		result := 0 - opdVal
-		opdStack.Push(efp.Token{TValue: fmt.Sprintf("%g", result), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
-	}
-	tokenCalcFunc := map[string]func(rOpd, lOpd string, opdStack *Stack) error{
-		"^":  calcPow,
-		"*":  calcMultiply,
-		"/":  calcDiv,
-		"+":  calcAdd,
-		"=":  calcEq,
-		"<>": calcNEq,
-		"<":  calcL,
-		"<=": calcLe,
-		">":  calcG,
-		">=": calcGe,
-		"&":  calcSplice,
-	}
-	if opt.TValue == "-" && opt.TType == efp.TokenTypeOperatorInfix {
-		if opdStack.Len() < 2 {
-			return ErrInvalidFormula
-		}
-		rOpd := opdStack.Pop().(efp.Token)
-		lOpd := opdStack.Pop().(efp.Token)
-		if err := calcSubtract(rOpd.TValue, lOpd.TValue, opdStack); err != nil {
-			return err
-		}
-	}
-	fn, ok := tokenCalcFunc[opt.TValue]
-	if ok {
-		if opdStack.Len() < 2 {
-			return ErrInvalidFormula
-		}
-		rOpd := opdStack.Pop().(efp.Token)
-		lOpd := opdStack.Pop().(efp.Token)
-		if err := fn(rOpd.TValue, lOpd.TValue, opdStack); err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
-// parseOperatorPrefixToken parse operator prefix token.
-func (f *File) parseOperatorPrefixToken(optStack, opdStack *Stack, token efp.Token) (err error) {
-	if optStack.Len() == 0 {
-		optStack.Push(token)
-	} else {
-		tokenPriority := getPriority(token)
-		topOpt := optStack.Peek().(efp.Token)
-		topOptPriority := getPriority(topOpt)
-		if tokenPriority > topOptPriority {
-			optStack.Push(token)
-		} else {
-			for tokenPriority <= topOptPriority {
-				optStack.Pop()
-				if err = calculate(opdStack, topOpt); err != nil {
-					return
-				}
-				if optStack.Len() > 0 {
-					topOpt = optStack.Peek().(efp.Token)
-					topOptPriority = getPriority(topOpt)
-					continue
-				}
-				break
-			}
-			optStack.Push(token)
-		}
-	}
-	return
-}
-
-// isFunctionStartToken determine if the token is function stop.
-func isFunctionStartToken(token efp.Token) bool {
-	return token.TType == efp.TokenTypeFunction && token.TSubType == efp.TokenSubTypeStart
-}
-
-// isFunctionStopToken determine if the token is function stop.
-func isFunctionStopToken(token efp.Token) bool {
-	return token.TType == efp.TokenTypeFunction && token.TSubType == efp.TokenSubTypeStop
-}
-
-// isBeginParenthesesToken determine if the token is begin parentheses: (.
-func isBeginParenthesesToken(token efp.Token) bool {
-	return token.TType == efp.TokenTypeSubexpression && token.TSubType == efp.TokenSubTypeStart
-}
-
-// isEndParenthesesToken determine if the token is end parentheses: ).
-func isEndParenthesesToken(token efp.Token) bool {
-	return token.TType == efp.TokenTypeSubexpression && token.TSubType == efp.TokenSubTypeStop
-}
-
-// isOperatorPrefixToken determine if the token is parse operator prefix
-// token.
-func isOperatorPrefixToken(token efp.Token) bool {
-	_, ok := tokenPriority[token.TValue]
-	if (token.TValue == "-" && token.TType == efp.TokenTypeOperatorPrefix) || (ok && token.TType == efp.TokenTypeOperatorInfix) {
-		return true
-	}
-	return false
-}
-
-// getDefinedNameRefTo convert defined name to reference range.
-func (f *File) getDefinedNameRefTo(definedNameName string, currentSheet string) (refTo string) {
-	for _, definedName := range f.GetDefinedName() {
-		if definedName.Name == definedNameName {
-			refTo = definedName.RefersTo
-			// worksheet scope takes precedence over scope workbook when both definedNames exist
-			if definedName.Scope == currentSheet {
-				break
-			}
-		}
-	}
-	return refTo
-}
-
-// parseToken parse basic arithmetic operator priority and evaluate based on
-// operators and operands.
-func (f *File) parseToken(sheet string, token efp.Token, opdStack, optStack *Stack) error {
-	// parse reference: must reference at here
-	if token.TSubType == efp.TokenSubTypeRange {
-		refTo := f.getDefinedNameRefTo(token.TValue, sheet)
-		if refTo != "" {
-			token.TValue = refTo
-		}
-		result, err := f.parseReference(sheet, token.TValue)
-		if err != nil {
-			return errors.New(formulaErrorNAME)
-		}
-		if result.Type != ArgString {
-			return errors.New(formulaErrorVALUE)
-		}
-		token.TValue = result.String
-		token.TType = efp.TokenTypeOperand
-		token.TSubType = efp.TokenSubTypeNumber
-	}
-	if isOperatorPrefixToken(token) {
-		if err := f.parseOperatorPrefixToken(optStack, opdStack, token); err != nil {
-			return err
-		}
-	}
-	if isBeginParenthesesToken(token) { // (
-		optStack.Push(token)
-	}
-	if isEndParenthesesToken(token) { // )
-		for !isBeginParenthesesToken(optStack.Peek().(efp.Token)) { // != (
-			topOpt := optStack.Peek().(efp.Token)
-			if err := calculate(opdStack, topOpt); err != nil {
-				return err
-			}
-			optStack.Pop()
-		}
-		optStack.Pop()
-	}
-	// opd
-	if token.TType == efp.TokenTypeOperand && token.TSubType == efp.TokenSubTypeNumber {
-		opdStack.Push(token)
-	}
-	return nil
-}
-
-// parseReference parse reference and extract values by given reference
-// characters and default sheet name.
-func (f *File) parseReference(sheet, reference string) (arg formulaArg, err error) {
-	reference = strings.Replace(reference, "$", "", -1)
-	refs, cellRanges, cellRefs := list.New(), list.New(), list.New()
-	for _, ref := range strings.Split(reference, ":") {
-		tokens := strings.Split(ref, "!")
-		cr := cellRef{}
-		if len(tokens) == 2 { // have a worksheet name
-			cr.Sheet = tokens[0]
-			// cast to cell coordinates
-			if cr.Col, cr.Row, err = CellNameToCoordinates(tokens[1]); err != nil {
-				// cast to column
-				if cr.Col, err = ColumnNameToNumber(tokens[1]); err != nil {
-					// cast to row
-					if cr.Row, err = strconv.Atoi(tokens[1]); err != nil {
-						err = newInvalidColumnNameError(tokens[1])
-						return
-					}
-					cr.Col = TotalColumns
-				}
-			}
-			if refs.Len() > 0 {
-				e := refs.Back()
-				cellRefs.PushBack(e.Value.(cellRef))
-				refs.Remove(e)
-			}
-			refs.PushBack(cr)
-			continue
-		}
-		// cast to cell coordinates
-		if cr.Col, cr.Row, err = CellNameToCoordinates(tokens[0]); err != nil {
-			// cast to column
-			if cr.Col, err = ColumnNameToNumber(tokens[0]); err != nil {
-				// cast to row
-				if cr.Row, err = strconv.Atoi(tokens[0]); err != nil {
-					err = newInvalidColumnNameError(tokens[0])
-					return
-				}
-				cr.Col = TotalColumns
-			}
-			cellRanges.PushBack(cellRange{
-				From: cellRef{Sheet: sheet, Col: cr.Col, Row: 1},
-				To:   cellRef{Sheet: sheet, Col: cr.Col, Row: TotalRows},
-			})
-			cellRefs.Init()
-			arg, err = f.rangeResolver(cellRefs, cellRanges)
-			return
-		}
-		e := refs.Back()
-		if e == nil {
-			cr.Sheet = sheet
-			refs.PushBack(cr)
-			continue
-		}
-		cellRanges.PushBack(cellRange{
-			From: e.Value.(cellRef),
-			To:   cr,
-		})
-		refs.Remove(e)
-	}
-	if refs.Len() > 0 {
-		e := refs.Back()
-		cellRefs.PushBack(e.Value.(cellRef))
-		refs.Remove(e)
-	}
-	arg, err = f.rangeResolver(cellRefs, cellRanges)
-	return
-}
-
-// prepareValueRange prepare value range.
-func prepareValueRange(cr cellRange, valueRange []int) {
-	if cr.From.Row < valueRange[0] || valueRange[0] == 0 {
-		valueRange[0] = cr.From.Row
-	}
-	if cr.From.Col < valueRange[2] || valueRange[2] == 0 {
-		valueRange[2] = cr.From.Col
-	}
-	if cr.To.Row > valueRange[1] || valueRange[1] == 0 {
-		valueRange[1] = cr.To.Row
-	}
-	if cr.To.Col > valueRange[3] || valueRange[3] == 0 {
-		valueRange[3] = cr.To.Col
-	}
-}
-
-// prepareValueRef prepare value reference.
-func prepareValueRef(cr cellRef, valueRange []int) {
-	if cr.Row < valueRange[0] || valueRange[0] == 0 {
-		valueRange[0] = cr.Row
-	}
-	if cr.Col < valueRange[2] || valueRange[2] == 0 {
-		valueRange[2] = cr.Col
-	}
-	if cr.Row > valueRange[1] || valueRange[1] == 0 {
-		valueRange[1] = cr.Row
-	}
-	if cr.Col > valueRange[3] || valueRange[3] == 0 {
-		valueRange[3] = cr.Col
-	}
-}
-
-// rangeResolver extract value as string from given reference and range list.
-// This function will not ignore the empty cell. For example, A1:A2:A2:B3 will
-// be reference A1:B3.
-func (f *File) rangeResolver(cellRefs, cellRanges *list.List) (arg formulaArg, err error) {
-	arg.cellRefs, arg.cellRanges = cellRefs, cellRanges
-	// value range order: from row, to row, from column, to column
-	valueRange := []int{0, 0, 0, 0}
-	var sheet string
-	// prepare value range
-	for temp := cellRanges.Front(); temp != nil; temp = temp.Next() {
-		cr := temp.Value.(cellRange)
-		if cr.From.Sheet != cr.To.Sheet {
-			err = errors.New(formulaErrorVALUE)
-		}
-		rng := []int{cr.From.Col, cr.From.Row, cr.To.Col, cr.To.Row}
-		_ = sortCoordinates(rng)
-		cr.From.Col, cr.From.Row, cr.To.Col, cr.To.Row = rng[0], rng[1], rng[2], rng[3]
-		prepareValueRange(cr, valueRange)
-		if cr.From.Sheet != "" {
-			sheet = cr.From.Sheet
-		}
-	}
-	for temp := cellRefs.Front(); temp != nil; temp = temp.Next() {
-		cr := temp.Value.(cellRef)
-		if cr.Sheet != "" {
-			sheet = cr.Sheet
-		}
-		prepareValueRef(cr, valueRange)
-	}
-	// extract value from ranges
-	if cellRanges.Len() > 0 {
-		arg.Type = ArgMatrix
-		for row := valueRange[0]; row <= valueRange[1]; row++ {
-			var matrixRow = []formulaArg{}
-			for col := valueRange[2]; col <= valueRange[3]; col++ {
-				var cell, value string
-				if cell, err = CoordinatesToCellName(col, row); err != nil {
-					return
-				}
-				if value, err = f.GetCellValue(sheet, cell); err != nil {
-					return
-				}
-				matrixRow = append(matrixRow, formulaArg{
-					String: value,
-					Type:   ArgString,
-				})
-			}
-			arg.Matrix = append(arg.Matrix, matrixRow)
-		}
-		return
-	}
-	// extract value from references
-	for temp := cellRefs.Front(); temp != nil; temp = temp.Next() {
-		cr := temp.Value.(cellRef)
-		var cell string
-		if cell, err = CoordinatesToCellName(cr.Col, cr.Row); err != nil {
-			return
-		}
-		if arg.String, err = f.GetCellValue(cr.Sheet, cell); err != nil {
-			return
-		}
-		arg.Type = ArgString
-	}
-	return
-}
-
-// callFuncByName calls the no error or only error return function with
-// reflect by given receiver, name and parameters.
-func callFuncByName(receiver interface{}, name string, params []reflect.Value) (arg formulaArg) {
-	function := reflect.ValueOf(receiver).MethodByName(name)
-	if function.IsValid() {
-		rt := function.Call(params)
-		if len(rt) == 0 {
-			return
-		}
-		arg = rt[0].Interface().(formulaArg)
-		return
-	}
-	return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("not support %s function", name))
-}
-
-// formulaCriteriaParser parse formula criteria.
-func formulaCriteriaParser(exp string) (fc *formulaCriteria) {
-	fc = &formulaCriteria{}
-	if exp == "" {
-		return
-	}
-	if match := regexp.MustCompile(`^([0-9]+)$`).FindStringSubmatch(exp); len(match) > 1 {
-		fc.Type, fc.Condition = criteriaEq, match[1]
-		return
-	}
-	if match := regexp.MustCompile(`^=(.*)$`).FindStringSubmatch(exp); len(match) > 1 {
-		fc.Type, fc.Condition = criteriaEq, match[1]
-		return
-	}
-	if match := regexp.MustCompile(`^<=(.*)$`).FindStringSubmatch(exp); len(match) > 1 {
-		fc.Type, fc.Condition = criteriaLe, match[1]
-		return
-	}
-	if match := regexp.MustCompile(`^>=(.*)$`).FindStringSubmatch(exp); len(match) > 1 {
-		fc.Type, fc.Condition = criteriaGe, match[1]
-		return
-	}
-	if match := regexp.MustCompile(`^<(.*)$`).FindStringSubmatch(exp); len(match) > 1 {
-		fc.Type, fc.Condition = criteriaL, match[1]
-		return
-	}
-	if match := regexp.MustCompile(`^>(.*)$`).FindStringSubmatch(exp); len(match) > 1 {
-		fc.Type, fc.Condition = criteriaG, match[1]
-		return
-	}
-	if strings.Contains(exp, "*") {
-		if strings.HasPrefix(exp, "*") {
-			fc.Type, fc.Condition = criteriaEnd, strings.TrimPrefix(exp, "*")
-		}
-		if strings.HasSuffix(exp, "*") {
-			fc.Type, fc.Condition = criteriaBeg, strings.TrimSuffix(exp, "*")
-		}
-		return
-	}
-	fc.Type, fc.Condition = criteriaEq, exp
-	return
-}
-
-// formulaCriteriaEval evaluate formula criteria expression.
-func formulaCriteriaEval(val string, criteria *formulaCriteria) (result bool, err error) {
-	var value, expected float64
-	var e error
-	var prepareValue = func(val, cond string) (value float64, expected float64, err error) {
-		if value, err = strconv.ParseFloat(val, 64); err != nil {
-			return
-		}
-		if expected, err = strconv.ParseFloat(criteria.Condition, 64); err != nil {
-			return
-		}
-		return
-	}
-	switch criteria.Type {
-	case criteriaEq:
-		return val == criteria.Condition, err
-	case criteriaLe:
-		value, expected, e = prepareValue(val, criteria.Condition)
-		return value <= expected && e == nil, err
-	case criteriaGe:
-		value, expected, e = prepareValue(val, criteria.Condition)
-		return value >= expected && e == nil, err
-	case criteriaL:
-		value, expected, e = prepareValue(val, criteria.Condition)
-		return value < expected && e == nil, err
-	case criteriaG:
-		value, expected, e = prepareValue(val, criteria.Condition)
-		return value > expected && e == nil, err
-	case criteriaBeg:
-		return strings.HasPrefix(val, criteria.Condition), err
-	case criteriaEnd:
-		return strings.HasSuffix(val, criteria.Condition), err
-	}
-	return
-}
-
-// Engineering Functions
-
-// BESSELI function the modified Bessel function, which is equivalent to the
-// Bessel function evaluated for purely imaginary arguments. The syntax of
-// the Besseli function is:
-//
-//    BESSELI(x,n)
-//
-func (fn *formulaFuncs) BESSELI(argsList *list.List) formulaArg {
-	if argsList.Len() != 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "BESSELI requires 2 numeric arguments")
-	}
-	return fn.bassel(argsList, true)
-}
-
-// BESSELJ function returns the Bessel function, Jn(x), for a specified order
-// and value of x. The syntax of the function is:
-//
-//    BESSELJ(x,n)
-//
-func (fn *formulaFuncs) BESSELJ(argsList *list.List) formulaArg {
-	if argsList.Len() != 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "BESSELJ requires 2 numeric arguments")
-	}
-	return fn.bassel(argsList, false)
-}
-
-// bassel is an implementation of the formula function BESSELI and BESSELJ.
-func (fn *formulaFuncs) bassel(argsList *list.List, modfied bool) formulaArg {
-	x, n := argsList.Front().Value.(formulaArg).ToNumber(), argsList.Back().Value.(formulaArg).ToNumber()
-	if x.Type != ArgNumber {
-		return x
-	}
-	if n.Type != ArgNumber {
-		return n
-	}
-	max, x1 := 100, x.Number*0.5
-	x2 := x1 * x1
-	x1 = math.Pow(x1, n.Number)
-	n1, n2, n3, n4, add := fact(n.Number), 1.0, 0.0, n.Number, false
-	result := x1 / n1
-	t := result * 0.9
-	for result != t && max != 0 {
-		x1 *= x2
-		n3++
-		n1 *= n3
-		n4++
-		n2 *= n4
-		t = result
-		if modfied || add {
-			result += (x1 / n1 / n2)
-		} else {
-			result -= (x1 / n1 / n2)
-		}
-		max--
-		add = !add
-	}
-	return newNumberFormulaArg(result)
-}
-
-// BESSELK function calculates the modified Bessel functions, Kn(x), which are
-// also known as the hyperbolic Bessel Functions. These are the equivalent of
-// the Bessel functions, evaluated for purely imaginary arguments. The syntax
-// of the function is:
-//
-//    BESSELK(x,n)
-//
-func (fn *formulaFuncs) BESSELK(argsList *list.List) formulaArg {
-	if argsList.Len() != 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "BESSELK requires 2 numeric arguments")
-	}
-	x, n := argsList.Front().Value.(formulaArg).ToNumber(), argsList.Back().Value.(formulaArg).ToNumber()
-	if x.Type != ArgNumber {
-		return x
-	}
-	if n.Type != ArgNumber {
-		return n
-	}
-	if x.Number <= 0 || n.Number < 0 {
-		return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
-	}
-	var result float64
-	switch math.Floor(n.Number) {
-	case 0:
-		result = fn.besselK0(x)
-	case 1:
-		result = fn.besselK1(x)
-	default:
-		result = fn.besselK2(x, n)
-	}
-	return newNumberFormulaArg(result)
-}
-
-// besselK0 is an implementation of the formula function BESSELK.
-func (fn *formulaFuncs) besselK0(x formulaArg) float64 {
-	var y float64
-	if x.Number <= 2 {
-		n2 := x.Number * 0.5
-		y = n2 * n2
-		args := list.New()
-		args.PushBack(x)
-		args.PushBack(newNumberFormulaArg(0))
-		return -math.Log(n2)*fn.BESSELI(args).Number +
-			(-0.57721566 + y*(0.42278420+y*(0.23069756+y*(0.3488590e-1+y*(0.262698e-2+y*
-				(0.10750e-3+y*0.74e-5))))))
-	}
-	y = 2 / x.Number
-	return math.Exp(-x.Number) / math.Sqrt(x.Number) *
-		(1.25331414 + y*(-0.7832358e-1+y*(0.2189568e-1+y*(-0.1062446e-1+y*
-			(0.587872e-2+y*(-0.251540e-2+y*0.53208e-3))))))
-}
-
-// besselK1 is an implementation of the formula function BESSELK.
-func (fn *formulaFuncs) besselK1(x formulaArg) float64 {
-	var n2, y float64
-	if x.Number <= 2 {
-		n2 = x.Number * 0.5
-		y = n2 * n2
-		args := list.New()
-		args.PushBack(x)
-		args.PushBack(newNumberFormulaArg(1))
-		return math.Log(n2)*fn.BESSELI(args).Number +
-			(1+y*(0.15443144+y*(-0.67278579+y*(-0.18156897+y*(-0.1919402e-1+y*(-0.110404e-2+y*(-0.4686e-4)))))))/x.Number
-	}
-	y = 2 / x.Number
-	return math.Exp(-x.Number) / math.Sqrt(x.Number) *
-		(1.25331414 + y*(0.23498619+y*(-0.3655620e-1+y*(0.1504268e-1+y*(-0.780353e-2+y*
-			(0.325614e-2+y*(-0.68245e-3)))))))
-}
-
-// besselK2 is an implementation of the formula function BESSELK.
-func (fn *formulaFuncs) besselK2(x, n formulaArg) float64 {
-	tox, bkm, bk, bkp := 2/x.Number, fn.besselK0(x), fn.besselK1(x), 0.0
-	for i := 1.0; i < n.Number; i++ {
-		bkp = bkm + i*tox*bk
-		bkm = bk
-		bk = bkp
-	}
-	return bk
-}
-
-// BESSELY function returns the Bessel function, Yn(x), (also known as the
-// Weber function or the Neumann function), for a specified order and value
-// of x. The syntax of the function is:
-//
-//    BESSELY(x,n)
-//
-func (fn *formulaFuncs) BESSELY(argsList *list.List) formulaArg {
-	if argsList.Len() != 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "BESSELY requires 2 numeric arguments")
-	}
-	x, n := argsList.Front().Value.(formulaArg).ToNumber(), argsList.Back().Value.(formulaArg).ToNumber()
-	if x.Type != ArgNumber {
-		return x
-	}
-	if n.Type != ArgNumber {
-		return n
-	}
-	if x.Number <= 0 || n.Number < 0 {
-		return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
-	}
-	var result float64
-	switch math.Floor(n.Number) {
-	case 0:
-		result = fn.besselY0(x)
-	case 1:
-		result = fn.besselY1(x)
-	default:
-		result = fn.besselY2(x, n)
-	}
-	return newNumberFormulaArg(result)
-}
-
-// besselY0 is an implementation of the formula function BESSELY.
-func (fn *formulaFuncs) besselY0(x formulaArg) float64 {
-	var y float64
-	if x.Number < 8 {
-		y = x.Number * x.Number
-		f1 := -2957821389.0 + y*(7062834065.0+y*(-512359803.6+y*(10879881.29+y*
-			(-86327.92757+y*228.4622733))))
-		f2 := 40076544269.0 + y*(745249964.8+y*(7189466.438+y*
-			(47447.26470+y*(226.1030244+y))))
-		args := list.New()
-		args.PushBack(x)
-		args.PushBack(newNumberFormulaArg(0))
-		return f1/f2 + 0.636619772*fn.BESSELJ(args).Number*math.Log(x.Number)
-	}
-	z := 8.0 / x.Number
-	y = z * z
-	xx := x.Number - 0.785398164
-	f1 := 1 + y*(-0.1098628627e-2+y*(0.2734510407e-4+y*(-0.2073370639e-5+y*0.2093887211e-6)))
-	f2 := -0.1562499995e-1 + y*(0.1430488765e-3+y*(-0.6911147651e-5+y*(0.7621095161e-6+y*
-		(-0.934945152e-7))))
-	return math.Sqrt(0.636619772/x.Number) * (math.Sin(xx)*f1 + z*math.Cos(xx)*f2)
-}
-
-// besselY1 is an implementation of the formula function BESSELY.
-func (fn *formulaFuncs) besselY1(x formulaArg) float64 {
-	if x.Number < 8 {
-		y := x.Number * x.Number
-		f1 := x.Number * (-0.4900604943e13 + y*(0.1275274390e13+y*(-0.5153438139e11+y*
-			(0.7349264551e9+y*(-0.4237922726e7+y*0.8511937935e4)))))
-		f2 := 0.2499580570e14 + y*(0.4244419664e12+y*(0.3733650367e10+y*(0.2245904002e8+y*
-			(0.1020426050e6+y*(0.3549632885e3+y)))))
-		args := list.New()
-		args.PushBack(x)
-		args.PushBack(newNumberFormulaArg(1))
-		return f1/f2 + 0.636619772*(fn.BESSELJ(args).Number*math.Log(x.Number)-1/x.Number)
-	}
-	return math.Sqrt(0.636619772/x.Number) * math.Sin(x.Number-2.356194491)
-}
-
-// besselY2 is an implementation of the formula function BESSELY.
-func (fn *formulaFuncs) besselY2(x, n formulaArg) float64 {
-	tox, bym, by, byp := 2/x.Number, fn.besselY0(x), fn.besselY1(x), 0.0
-	for i := 1.0; i < n.Number; i++ {
-		byp = i*tox*by - bym
-		bym = by
-		by = byp
-	}
-	return by
-}
-
-// BIN2DEC function converts a Binary (a base-2 number) into a decimal number.
-// The syntax of the function is:
-//
-//    BIN2DEC(number)
-//
-func (fn *formulaFuncs) BIN2DEC(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "BIN2DEC requires 1 numeric argument")
-	}
-	token := argsList.Front().Value.(formulaArg)
-	number := token.ToNumber()
-	if number.Type != ArgNumber {
-		return newErrorFormulaArg(formulaErrorVALUE, number.Error)
-	}
-	return fn.bin2dec(token.Value())
-}
-
-// BIN2HEX function converts a Binary (Base 2) number into a Hexadecimal
-// (Base 16) number. The syntax of the function is:
-//
-//    BIN2HEX(number,[places])
-//
-func (fn *formulaFuncs) BIN2HEX(argsList *list.List) formulaArg {
-	if argsList.Len() < 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "BIN2HEX requires at least 1 argument")
-	}
-	if argsList.Len() > 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "BIN2HEX allows at most 2 arguments")
-	}
-	token := argsList.Front().Value.(formulaArg)
-	number := token.ToNumber()
-	if number.Type != ArgNumber {
-		return newErrorFormulaArg(formulaErrorVALUE, number.Error)
-	}
-	decimal, newList := fn.bin2dec(token.Value()), list.New()
-	if decimal.Type != ArgNumber {
-		return decimal
-	}
-	newList.PushBack(decimal)
-	if argsList.Len() == 2 {
-		newList.PushBack(argsList.Back().Value.(formulaArg))
-	}
-	return fn.dec2x("BIN2HEX", newList)
-}
-
-// BIN2OCT function converts a Binary (Base 2) number into an Octal (Base 8)
-// number. The syntax of the function is:
-//
-//    BIN2OCT(number,[places])
-//
-func (fn *formulaFuncs) BIN2OCT(argsList *list.List) formulaArg {
-	if argsList.Len() < 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "BIN2OCT requires at least 1 argument")
-	}
-	if argsList.Len() > 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "BIN2OCT allows at most 2 arguments")
-	}
-	token := argsList.Front().Value.(formulaArg)
-	number := token.ToNumber()
-	if number.Type != ArgNumber {
-		return newErrorFormulaArg(formulaErrorVALUE, number.Error)
-	}
-	decimal, newList := fn.bin2dec(token.Value()), list.New()
-	if decimal.Type != ArgNumber {
-		return decimal
-	}
-	newList.PushBack(decimal)
-	if argsList.Len() == 2 {
-		newList.PushBack(argsList.Back().Value.(formulaArg))
-	}
-	return fn.dec2x("BIN2OCT", newList)
-}
-
-// bin2dec is an implementation of the formula function BIN2DEC.
-func (fn *formulaFuncs) bin2dec(number string) formulaArg {
-	decimal, length := 0.0, len(number)
-	for i := length; i > 0; i-- {
-		s := string(number[length-i])
-		if i == 10 && s == "1" {
-			decimal += math.Pow(-2.0, float64(i-1))
-			continue
-		}
-		if s == "1" {
-			decimal += math.Pow(2.0, float64(i-1))
-			continue
-		}
-		if s != "0" {
-			return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
-		}
-	}
-	return newNumberFormulaArg(decimal)
-}
-
-// BITAND function returns the bitwise 'AND' for two supplied integers. The
-// syntax of the function is:
-//
-//    BITAND(number1,number2)
-//
-func (fn *formulaFuncs) BITAND(argsList *list.List) formulaArg {
-	return fn.bitwise("BITAND", argsList)
-}
-
-// BITLSHIFT function returns a supplied integer, shifted left by a specified
-// number of bits. The syntax of the function is:
-//
-//    BITLSHIFT(number1,shift_amount)
-//
-func (fn *formulaFuncs) BITLSHIFT(argsList *list.List) formulaArg {
-	return fn.bitwise("BITLSHIFT", argsList)
-}
-
-// BITOR function returns the bitwise 'OR' for two supplied integers. The
-// syntax of the function is:
-//
-//    BITOR(number1,number2)
-//
-func (fn *formulaFuncs) BITOR(argsList *list.List) formulaArg {
-	return fn.bitwise("BITOR", argsList)
-}
-
-// BITRSHIFT function returns a supplied integer, shifted right by a specified
-// number of bits. The syntax of the function is:
-//
-//    BITRSHIFT(number1,shift_amount)
-//
-func (fn *formulaFuncs) BITRSHIFT(argsList *list.List) formulaArg {
-	return fn.bitwise("BITRSHIFT", argsList)
-}
-
-// BITXOR function returns the bitwise 'XOR' (exclusive 'OR') for two supplied
-// integers. The syntax of the function is:
-//
-//    BITXOR(number1,number2)
-//
-func (fn *formulaFuncs) BITXOR(argsList *list.List) formulaArg {
-	return fn.bitwise("BITXOR", argsList)
-}
-
-// bitwise is an implementation of the formula function BITAND, BITLSHIFT,
-// BITOR, BITRSHIFT and BITXOR.
-func (fn *formulaFuncs) bitwise(name string, argsList *list.List) formulaArg {
-	if argsList.Len() != 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 2 numeric arguments", name))
-	}
-	num1, num2 := argsList.Front().Value.(formulaArg).ToNumber(), argsList.Back().Value.(formulaArg).ToNumber()
-	if num1.Type != ArgNumber || num2.Type != ArgNumber {
-		return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
-	}
-	max := math.Pow(2, 48) - 1
-	if num1.Number < 0 || num1.Number > max || num2.Number < 0 || num2.Number > max {
-		return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
-	}
-	bitwiseFuncMap := map[string]func(a, b int) int{
-		"BITAND":    func(a, b int) int { return a & b },
-		"BITLSHIFT": func(a, b int) int { return a << uint(b) },
-		"BITOR":     func(a, b int) int { return a | b },
-		"BITRSHIFT": func(a, b int) int { return a >> uint(b) },
-		"BITXOR":    func(a, b int) int { return a ^ b },
-	}
-	bitwiseFunc := bitwiseFuncMap[name]
-	return newNumberFormulaArg(float64(bitwiseFunc(int(num1.Number), int(num2.Number))))
-}
-
-// COMPLEX function takes two arguments, representing the real and the
-// imaginary coefficients of a complex number, and from these, creates a
-// complex number. The syntax of the function is:
-//
-//    COMPLEX(real_num,i_num,[suffix])
-//
-func (fn *formulaFuncs) COMPLEX(argsList *list.List) formulaArg {
-	if argsList.Len() < 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "COMPLEX requires at least 2 arguments")
-	}
-	if argsList.Len() > 3 {
-		return newErrorFormulaArg(formulaErrorVALUE, "COMPLEX allows at most 3 arguments")
-	}
-	real, i, suffix := argsList.Front().Value.(formulaArg).ToNumber(), argsList.Front().Next().Value.(formulaArg).ToNumber(), "i"
-	if real.Type != ArgNumber {
-		return real
-	}
-	if i.Type != ArgNumber {
-		return i
-	}
-	if argsList.Len() == 3 {
-		if suffix = strings.ToLower(argsList.Back().Value.(formulaArg).Value()); suffix != "i" && suffix != "j" {
-			return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
-		}
-	}
-	return newStringFormulaArg(cmplx2str(fmt.Sprint(complex(real.Number, i.Number)), suffix))
-}
-
-// cmplx2str replace complex number string characters.
-func cmplx2str(c, suffix string) string {
-	if c == "(0+0i)" || c == "(-0+0i)" || c == "(0-0i)" || c == "(-0-0i)" {
-		return "0"
-	}
-	c = strings.TrimPrefix(c, "(")
-	c = strings.TrimPrefix(c, "+0+")
-	c = strings.TrimPrefix(c, "-0+")
-	c = strings.TrimSuffix(c, ")")
-	c = strings.TrimPrefix(c, "0+")
-	if strings.HasPrefix(c, "0-") {
-		c = "-" + strings.TrimPrefix(c, "0-")
-	}
-	c = strings.TrimPrefix(c, "0+")
-	c = strings.TrimSuffix(c, "+0i")
-	c = strings.TrimSuffix(c, "-0i")
-	c = strings.NewReplacer("+1i", "+i", "-1i", "-i").Replace(c)
-	c = strings.Replace(c, "i", suffix, -1)
-	return c
-}
-
-// str2cmplx convert complex number string characters.
-func str2cmplx(c string) string {
-	c = strings.Replace(c, "j", "i", -1)
-	if c == "i" {
-		c = "1i"
-	}
-	c = strings.NewReplacer("+i", "+1i", "-i", "-1i").Replace(c)
-	return c
-}
-
-// DEC2BIN function converts a decimal number into a Binary (Base 2) number.
-// The syntax of the function is:
-//
-//    DEC2BIN(number,[places])
-//
-func (fn *formulaFuncs) DEC2BIN(argsList *list.List) formulaArg {
-	return fn.dec2x("DEC2BIN", argsList)
-}
-
-// DEC2HEX function converts a decimal number into a Hexadecimal (Base 16)
-// number. The syntax of the function is:
-//
-//    DEC2HEX(number,[places])
-//
-func (fn *formulaFuncs) DEC2HEX(argsList *list.List) formulaArg {
-	return fn.dec2x("DEC2HEX", argsList)
-}
-
-// DEC2OCT function converts a decimal number into an Octal (Base 8) number.
-// The syntax of the function is:
-//
-//    DEC2OCT(number,[places])
-//
-func (fn *formulaFuncs) DEC2OCT(argsList *list.List) formulaArg {
-	return fn.dec2x("DEC2OCT", argsList)
-}
-
-// dec2x is an implementation of the formula function DEC2BIN, DEC2HEX and
-// DEC2OCT.
-func (fn *formulaFuncs) dec2x(name string, argsList *list.List) formulaArg {
-	if argsList.Len() < 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at least 1 argument", name))
-	}
-	if argsList.Len() > 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s allows at most 2 arguments", name))
-	}
-	decimal := argsList.Front().Value.(formulaArg).ToNumber()
-	if decimal.Type != ArgNumber {
-		return newErrorFormulaArg(formulaErrorVALUE, decimal.Error)
-	}
-	maxLimitMap := map[string]float64{
-		"DEC2BIN": 511,
-		"HEX2BIN": 511,
-		"OCT2BIN": 511,
-		"BIN2HEX": 549755813887,
-		"DEC2HEX": 549755813887,
-		"OCT2HEX": 549755813887,
-		"BIN2OCT": 536870911,
-		"DEC2OCT": 536870911,
-		"HEX2OCT": 536870911,
-	}
-	minLimitMap := map[string]float64{
-		"DEC2BIN": -512,
-		"HEX2BIN": -512,
-		"OCT2BIN": -512,
-		"BIN2HEX": -549755813888,
-		"DEC2HEX": -549755813888,
-		"OCT2HEX": -549755813888,
-		"BIN2OCT": -536870912,
-		"DEC2OCT": -536870912,
-		"HEX2OCT": -536870912,
-	}
-	baseMap := map[string]int{
-		"DEC2BIN": 2,
-		"HEX2BIN": 2,
-		"OCT2BIN": 2,
-		"BIN2HEX": 16,
-		"DEC2HEX": 16,
-		"OCT2HEX": 16,
-		"BIN2OCT": 8,
-		"DEC2OCT": 8,
-		"HEX2OCT": 8,
-	}
-	maxLimit, minLimit := maxLimitMap[name], minLimitMap[name]
-	base := baseMap[name]
-	if decimal.Number < minLimit || decimal.Number > maxLimit {
-		return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
-	}
-	n := int64(decimal.Number)
-	binary := strconv.FormatUint(*(*uint64)(unsafe.Pointer(&n)), base)
-	if argsList.Len() == 2 {
-		places := argsList.Back().Value.(formulaArg).ToNumber()
-		if places.Type != ArgNumber {
-			return newErrorFormulaArg(formulaErrorVALUE, places.Error)
-		}
-		binaryPlaces := len(binary)
-		if places.Number < 0 || places.Number > 10 || binaryPlaces > int(places.Number) {
-			return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
-		}
-		return newStringFormulaArg(strings.ToUpper(fmt.Sprintf("%s%s", strings.Repeat("0", int(places.Number)-binaryPlaces), binary)))
-	}
-	if decimal.Number < 0 && len(binary) > 10 {
-		return newStringFormulaArg(strings.ToUpper(binary[len(binary)-10:]))
-	}
-	return newStringFormulaArg(strings.ToUpper(binary))
-}
-
-// HEX2BIN function converts a Hexadecimal (Base 16) number into a Binary
-// (Base 2) number. The syntax of the function is:
-//
-//    HEX2BIN(number,[places])
-//
-func (fn *formulaFuncs) HEX2BIN(argsList *list.List) formulaArg {
-	if argsList.Len() < 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "HEX2BIN requires at least 1 argument")
-	}
-	if argsList.Len() > 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "HEX2BIN allows at most 2 arguments")
-	}
-	decimal, newList := fn.hex2dec(argsList.Front().Value.(formulaArg).Value()), list.New()
-	if decimal.Type != ArgNumber {
-		return decimal
-	}
-	newList.PushBack(decimal)
-	if argsList.Len() == 2 {
-		newList.PushBack(argsList.Back().Value.(formulaArg))
-	}
-	return fn.dec2x("HEX2BIN", newList)
-}
-
-// HEX2DEC function converts a hexadecimal (a base-16 number) into a decimal
-// number. The syntax of the function is:
-//
-//    HEX2DEC(number)
-//
-func (fn *formulaFuncs) HEX2DEC(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "HEX2DEC requires 1 numeric argument")
-	}
-	return fn.hex2dec(argsList.Front().Value.(formulaArg).Value())
-}
-
-// HEX2OCT function converts a Hexadecimal (Base 16) number into an Octal
-// (Base 8) number. The syntax of the function is:
-//
-//    HEX2OCT(number,[places])
-//
-func (fn *formulaFuncs) HEX2OCT(argsList *list.List) formulaArg {
-	if argsList.Len() < 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "HEX2OCT requires at least 1 argument")
-	}
-	if argsList.Len() > 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "HEX2OCT allows at most 2 arguments")
-	}
-	decimal, newList := fn.hex2dec(argsList.Front().Value.(formulaArg).Value()), list.New()
-	if decimal.Type != ArgNumber {
-		return decimal
-	}
-	newList.PushBack(decimal)
-	if argsList.Len() == 2 {
-		newList.PushBack(argsList.Back().Value.(formulaArg))
-	}
-	return fn.dec2x("HEX2OCT", newList)
-}
-
-// hex2dec is an implementation of the formula function HEX2DEC.
-func (fn *formulaFuncs) hex2dec(number string) formulaArg {
-	decimal, length := 0.0, len(number)
-	for i := length; i > 0; i-- {
-		num, err := strconv.ParseInt(string(number[length-i]), 16, 64)
-		if err != nil {
-			return newErrorFormulaArg(formulaErrorNUM, err.Error())
-		}
-		if i == 10 && string(number[length-i]) == "F" {
-			decimal += math.Pow(-16.0, float64(i-1))
-			continue
-		}
-		decimal += float64(num) * math.Pow(16.0, float64(i-1))
-	}
-	return newNumberFormulaArg(decimal)
-}
-
-// IMABS function returns the absolute value (the modulus) of a complex
-// number. The syntax of the function is:
-//
-//    IMABS(inumber)
-//
-func (fn *formulaFuncs) IMABS(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "IMABS requires 1 argument")
-	}
-	inumber, err := strconv.ParseComplex(str2cmplx(argsList.Front().Value.(formulaArg).Value()), 128)
-	if err != nil {
-		return newErrorFormulaArg(formulaErrorNUM, err.Error())
-	}
-	return newNumberFormulaArg(cmplx.Abs(inumber))
-}
-
-// IMAGINARY function returns the imaginary coefficient of a supplied complex
-// number. The syntax of the function is:
-//
-//    IMAGINARY(inumber)
-//
-func (fn *formulaFuncs) IMAGINARY(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "IMAGINARY requires 1 argument")
-	}
-	inumber, err := strconv.ParseComplex(str2cmplx(argsList.Front().Value.(formulaArg).Value()), 128)
-	if err != nil {
-		return newErrorFormulaArg(formulaErrorNUM, err.Error())
-	}
-	return newNumberFormulaArg(imag(inumber))
-}
-
-// IMARGUMENT function returns the phase (also called the argument) of a
-// supplied complex number. The syntax of the function is:
-//
-//    IMARGUMENT(inumber)
-//
-func (fn *formulaFuncs) IMARGUMENT(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "IMARGUMENT requires 1 argument")
-	}
-	inumber, err := strconv.ParseComplex(str2cmplx(argsList.Front().Value.(formulaArg).Value()), 128)
-	if err != nil {
-		return newErrorFormulaArg(formulaErrorNUM, err.Error())
-	}
-	return newNumberFormulaArg(cmplx.Phase(inumber))
-}
-
-// IMCONJUGATE function returns the complex conjugate of a supplied complex
-// number. The syntax of the function is:
-//
-//    IMCONJUGATE(inumber)
-//
-func (fn *formulaFuncs) IMCONJUGATE(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "IMCONJUGATE requires 1 argument")
-	}
-	inumber, err := strconv.ParseComplex(str2cmplx(argsList.Front().Value.(formulaArg).Value()), 128)
-	if err != nil {
-		return newErrorFormulaArg(formulaErrorNUM, err.Error())
-	}
-	return newStringFormulaArg(cmplx2str(fmt.Sprint(cmplx.Conj(inumber)), "i"))
-}
-
-// IMCOS function returns the cosine of a supplied complex number. The syntax
-// of the function is:
-//
-//    IMCOS(inumber)
-//
-func (fn *formulaFuncs) IMCOS(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "IMCOS requires 1 argument")
-	}
-	inumber, err := strconv.ParseComplex(str2cmplx(argsList.Front().Value.(formulaArg).Value()), 128)
-	if err != nil {
-		return newErrorFormulaArg(formulaErrorNUM, err.Error())
-	}
-	return newStringFormulaArg(cmplx2str(fmt.Sprint(cmplx.Cos(inumber)), "i"))
-}
-
-// IMCOSH function returns the hyperbolic cosine of a supplied complex number. The syntax
-// of the function is:
-//
-//    IMCOSH(inumber)
-//
-func (fn *formulaFuncs) IMCOSH(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "IMCOSH requires 1 argument")
-	}
-	inumber, err := strconv.ParseComplex(str2cmplx(argsList.Front().Value.(formulaArg).Value()), 128)
-	if err != nil {
-		return newErrorFormulaArg(formulaErrorNUM, err.Error())
-	}
-	return newStringFormulaArg(cmplx2str(fmt.Sprint(cmplx.Cosh(inumber)), "i"))
-}
-
-// IMCOT function returns the cotangent of a supplied complex number. The syntax
-// of the function is:
-//
-//    IMCOT(inumber)
-//
-func (fn *formulaFuncs) IMCOT(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "IMCOT requires 1 argument")
-	}
-	inumber, err := strconv.ParseComplex(str2cmplx(argsList.Front().Value.(formulaArg).Value()), 128)
-	if err != nil {
-		return newErrorFormulaArg(formulaErrorNUM, err.Error())
-	}
-	return newStringFormulaArg(cmplx2str(fmt.Sprint(cmplx.Cot(inumber)), "i"))
-}
-
-// IMCSC function returns the cosecant of a supplied complex number. The syntax
-// of the function is:
-//
-//    IMCSC(inumber)
-//
-func (fn *formulaFuncs) IMCSC(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "IMCSC requires 1 argument")
-	}
-	inumber, err := strconv.ParseComplex(str2cmplx(argsList.Front().Value.(formulaArg).Value()), 128)
-	if err != nil {
-		return newErrorFormulaArg(formulaErrorNUM, err.Error())
-	}
-	num := 1 / cmplx.Sin(inumber)
-	if cmplx.IsInf(num) {
-		return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
-	}
-	return newStringFormulaArg(cmplx2str(fmt.Sprint(num), "i"))
-}
-
-// IMCSCH function returns the hyperbolic cosecant of a supplied complex
-// number. The syntax of the function is:
-//
-//    IMCSCH(inumber)
-//
-func (fn *formulaFuncs) IMCSCH(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "IMCSCH requires 1 argument")
-	}
-	inumber, err := strconv.ParseComplex(str2cmplx(argsList.Front().Value.(formulaArg).Value()), 128)
-	if err != nil {
-		return newErrorFormulaArg(formulaErrorNUM, err.Error())
-	}
-	num := 1 / cmplx.Sinh(inumber)
-	if cmplx.IsInf(num) {
-		return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
-	}
-	return newStringFormulaArg(cmplx2str(fmt.Sprint(num), "i"))
-}
-
-// IMDIV function calculates the quotient of two complex numbers (i.e. divides
-// one complex number by another). The syntax of the function is:
-//
-//    IMDIV(inumber1,inumber2)
-//
-func (fn *formulaFuncs) IMDIV(argsList *list.List) formulaArg {
-	if argsList.Len() != 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "IMDIV requires 2 arguments")
-	}
-	inumber1, err := strconv.ParseComplex(str2cmplx(argsList.Front().Value.(formulaArg).Value()), 128)
-	if err != nil {
-		return newErrorFormulaArg(formulaErrorNUM, err.Error())
-	}
-	inumber2, err := strconv.ParseComplex(str2cmplx(argsList.Back().Value.(formulaArg).Value()), 128)
-	if err != nil {
-		return newErrorFormulaArg(formulaErrorNUM, err.Error())
-	}
-	num := inumber1 / inumber2
-	if cmplx.IsInf(num) {
-		return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
-	}
-	return newStringFormulaArg(cmplx2str(fmt.Sprint(num), "i"))
-}
-
-// IMEXP function returns the exponential of a supplied complex number. The
-// syntax of the function is:
-//
-//    IMEXP(inumber)
-//
-func (fn *formulaFuncs) IMEXP(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "IMEXP requires 1 argument")
-	}
-	inumber, err := strconv.ParseComplex(str2cmplx(argsList.Front().Value.(formulaArg).Value()), 128)
-	if err != nil {
-		return newErrorFormulaArg(formulaErrorNUM, err.Error())
-	}
-	return newStringFormulaArg(cmplx2str(fmt.Sprint(cmplx.Exp(inumber)), "i"))
-}
-
-// IMLN function returns the natural logarithm of a supplied complex number.
-// The syntax of the function is:
-//
-//    IMLN(inumber)
-//
-func (fn *formulaFuncs) IMLN(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "IMLN requires 1 argument")
-	}
-	inumber, err := strconv.ParseComplex(str2cmplx(argsList.Front().Value.(formulaArg).Value()), 128)
-	if err != nil {
-		return newErrorFormulaArg(formulaErrorNUM, err.Error())
-	}
-	num := cmplx.Log(inumber)
-	if cmplx.IsInf(num) {
-		return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
-	}
-	return newStringFormulaArg(cmplx2str(fmt.Sprint(num), "i"))
-}
-
-// IMLOG10 function returns the common (base 10) logarithm of a supplied
-// complex number. The syntax of the function is:
-//
-//    IMLOG10(inumber)
-//
-func (fn *formulaFuncs) IMLOG10(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "IMLOG10 requires 1 argument")
-	}
-	inumber, err := strconv.ParseComplex(str2cmplx(argsList.Front().Value.(formulaArg).Value()), 128)
-	if err != nil {
-		return newErrorFormulaArg(formulaErrorNUM, err.Error())
-	}
-	num := cmplx.Log10(inumber)
-	if cmplx.IsInf(num) {
-		return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
-	}
-	return newStringFormulaArg(cmplx2str(fmt.Sprint(num), "i"))
-}
-
-// IMLOG2 function calculates the base 2 logarithm of a supplied complex
-// number. The syntax of the function is:
-//
-//    IMLOG2(inumber)
-//
-func (fn *formulaFuncs) IMLOG2(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "IMLOG2 requires 1 argument")
-	}
-	inumber, err := strconv.ParseComplex(str2cmplx(argsList.Front().Value.(formulaArg).Value()), 128)
-	if err != nil {
-		return newErrorFormulaArg(formulaErrorNUM, err.Error())
-	}
-	num := cmplx.Log(inumber)
-	if cmplx.IsInf(num) {
-		return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
-	}
-	return newStringFormulaArg(cmplx2str(fmt.Sprint(num/cmplx.Log(2)), "i"))
-}
-
-// IMPOWER function returns a supplied complex number, raised to a given
-// power. The syntax of the function is:
-//
-//    IMPOWER(inumber,number)
-//
-func (fn *formulaFuncs) IMPOWER(argsList *list.List) formulaArg {
-	if argsList.Len() != 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "IMPOWER requires 2 arguments")
-	}
-	inumber, err := strconv.ParseComplex(str2cmplx(argsList.Front().Value.(formulaArg).Value()), 128)
-	if err != nil {
-		return newErrorFormulaArg(formulaErrorNUM, err.Error())
-	}
-	number, err := strconv.ParseComplex(str2cmplx(argsList.Back().Value.(formulaArg).Value()), 128)
-	if err != nil {
-		return newErrorFormulaArg(formulaErrorNUM, err.Error())
-	}
-	if inumber == 0 && number == 0 {
-		return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
-	}
-	num := cmplx.Pow(inumber, number)
-	if cmplx.IsInf(num) {
-		return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
-	}
-	return newStringFormulaArg(cmplx2str(fmt.Sprint(num), "i"))
-}
-
-// IMPRODUCT function calculates the product of two or more complex numbers.
-// The syntax of the function is:
-//
-//    IMPRODUCT(number1,[number2],...)
-//
-func (fn *formulaFuncs) IMPRODUCT(argsList *list.List) formulaArg {
-	product := complex128(1)
-	for arg := argsList.Front(); arg != nil; arg = arg.Next() {
-		token := arg.Value.(formulaArg)
-		switch token.Type {
-		case ArgString:
-			if token.Value() == "" {
-				continue
-			}
-			val, err := strconv.ParseComplex(str2cmplx(token.Value()), 128)
-			if err != nil {
-				return newErrorFormulaArg(formulaErrorNUM, err.Error())
-			}
-			product = product * val
-		case ArgNumber:
-			product = product * complex(token.Number, 0)
-		case ArgMatrix:
-			for _, row := range token.Matrix {
-				for _, value := range row {
-					if value.Value() == "" {
-						continue
-					}
-					val, err := strconv.ParseComplex(str2cmplx(value.Value()), 128)
-					if err != nil {
-						return newErrorFormulaArg(formulaErrorNUM, err.Error())
-					}
-					product = product * val
-				}
-			}
-		}
-	}
-	return newStringFormulaArg(cmplx2str(fmt.Sprint(product), "i"))
-}
-
-// IMREAL function returns the real coefficient of a supplied complex number.
-// The syntax of the function is:
-//
-//    IMREAL(inumber)
-//
-func (fn *formulaFuncs) IMREAL(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "IMREAL requires 1 argument")
-	}
-	inumber, err := strconv.ParseComplex(str2cmplx(argsList.Front().Value.(formulaArg).Value()), 128)
-	if err != nil {
-		return newErrorFormulaArg(formulaErrorNUM, err.Error())
-	}
-	return newStringFormulaArg(cmplx2str(fmt.Sprint(real(inumber)), "i"))
-}
-
-// IMSEC function returns the secant of a supplied complex number. The syntax
-// of the function is:
-//
-//    IMSEC(inumber)
-//
-func (fn *formulaFuncs) IMSEC(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "IMSEC requires 1 argument")
-	}
-	inumber, err := strconv.ParseComplex(str2cmplx(argsList.Front().Value.(formulaArg).Value()), 128)
-	if err != nil {
-		return newErrorFormulaArg(formulaErrorNUM, err.Error())
-	}
-	return newStringFormulaArg(cmplx2str(fmt.Sprint(1/cmplx.Cos(inumber)), "i"))
-}
-
-// IMSECH function returns the hyperbolic secant of a supplied complex number.
-// The syntax of the function is:
-//
-//    IMSECH(inumber)
-//
-func (fn *formulaFuncs) IMSECH(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "IMSECH requires 1 argument")
-	}
-	inumber, err := strconv.ParseComplex(str2cmplx(argsList.Front().Value.(formulaArg).Value()), 128)
-	if err != nil {
-		return newErrorFormulaArg(formulaErrorNUM, err.Error())
-	}
-	return newStringFormulaArg(cmplx2str(fmt.Sprint(1/cmplx.Cosh(inumber)), "i"))
-}
-
-// IMSIN function returns the Sine of a supplied complex number. The syntax of
-// the function is:
-//
-//    IMSIN(inumber)
-//
-func (fn *formulaFuncs) IMSIN(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "IMSIN requires 1 argument")
-	}
-	inumber, err := strconv.ParseComplex(str2cmplx(argsList.Front().Value.(formulaArg).Value()), 128)
-	if err != nil {
-		return newErrorFormulaArg(formulaErrorNUM, err.Error())
-	}
-	return newStringFormulaArg(cmplx2str(fmt.Sprint(cmplx.Sin(inumber)), "i"))
-}
-
-// IMSINH function returns the hyperbolic sine of a supplied complex number.
-// The syntax of the function is:
-//
-//    IMSINH(inumber)
-//
-func (fn *formulaFuncs) IMSINH(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "IMSINH requires 1 argument")
-	}
-	inumber, err := strconv.ParseComplex(str2cmplx(argsList.Front().Value.(formulaArg).Value()), 128)
-	if err != nil {
-		return newErrorFormulaArg(formulaErrorNUM, err.Error())
-	}
-	return newStringFormulaArg(cmplx2str(fmt.Sprint(cmplx.Sinh(inumber)), "i"))
-}
-
-// IMSQRT function returns the square root of a supplied complex number. The
-// syntax of the function is:
-//
-//    IMSQRT(inumber)
-//
-func (fn *formulaFuncs) IMSQRT(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "IMSQRT requires 1 argument")
-	}
-	inumber, err := strconv.ParseComplex(str2cmplx(argsList.Front().Value.(formulaArg).Value()), 128)
-	if err != nil {
-		return newErrorFormulaArg(formulaErrorNUM, err.Error())
-	}
-	return newStringFormulaArg(cmplx2str(fmt.Sprint(cmplx.Sqrt(inumber)), "i"))
-}
-
-// IMSUB function calculates the difference between two complex numbers
-// (i.e. subtracts one complex number from another). The syntax of the
-// function is:
-//
-//    IMSUB(inumber1,inumber2)
-//
-func (fn *formulaFuncs) IMSUB(argsList *list.List) formulaArg {
-	if argsList.Len() != 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "IMSUB requires 2 arguments")
-	}
-	i1, err := strconv.ParseComplex(str2cmplx(argsList.Front().Value.(formulaArg).Value()), 128)
-	if err != nil {
-		return newErrorFormulaArg(formulaErrorNUM, err.Error())
-	}
-	i2, err := strconv.ParseComplex(str2cmplx(argsList.Back().Value.(formulaArg).Value()), 128)
-	if err != nil {
-		return newErrorFormulaArg(formulaErrorNUM, err.Error())
-	}
-	return newStringFormulaArg(cmplx2str(fmt.Sprint(i1-i2), "i"))
-}
-
-// IMSUM function calculates the sum of two or more complex numbers. The
-// syntax of the function is:
-//
-//    IMSUM(inumber1,inumber2,...)
-//
-func (fn *formulaFuncs) IMSUM(argsList *list.List) formulaArg {
-	if argsList.Len() < 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "IMSUM requires at least 1 argument")
-	}
-	var result complex128
-	for arg := argsList.Front(); arg != nil; arg = arg.Next() {
-		token := arg.Value.(formulaArg)
-		num, err := strconv.ParseComplex(str2cmplx(token.Value()), 128)
-		if err != nil {
-			return newErrorFormulaArg(formulaErrorNUM, err.Error())
-		}
-		result += num
-	}
-	return newStringFormulaArg(cmplx2str(fmt.Sprint(result), "i"))
-}
-
-// IMTAN function returns the tangent of a supplied complex number. The syntax
-// of the function is:
-//
-//    IMTAN(inumber)
-//
-func (fn *formulaFuncs) IMTAN(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "IMTAN requires 1 argument")
-	}
-	inumber, err := strconv.ParseComplex(str2cmplx(argsList.Front().Value.(formulaArg).Value()), 128)
-	if err != nil {
-		return newErrorFormulaArg(formulaErrorNUM, err.Error())
-	}
-	return newStringFormulaArg(cmplx2str(fmt.Sprint(cmplx.Tan(inumber)), "i"))
-}
-
-// OCT2BIN function converts an Octal (Base 8) number into a Binary (Base 2)
-// number. The syntax of the function is:
-//
-//    OCT2BIN(number,[places])
-//
-func (fn *formulaFuncs) OCT2BIN(argsList *list.List) formulaArg {
-	if argsList.Len() < 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "OCT2BIN requires at least 1 argument")
-	}
-	if argsList.Len() > 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "OCT2BIN allows at most 2 arguments")
-	}
-	token := argsList.Front().Value.(formulaArg)
-	number := token.ToNumber()
-	if number.Type != ArgNumber {
-		return newErrorFormulaArg(formulaErrorVALUE, number.Error)
-	}
-	decimal, newList := fn.oct2dec(token.Value()), list.New()
-	newList.PushBack(decimal)
-	if argsList.Len() == 2 {
-		newList.PushBack(argsList.Back().Value.(formulaArg))
-	}
-	return fn.dec2x("OCT2BIN", newList)
-}
-
-// OCT2DEC function converts an Octal (a base-8 number) into a decimal number.
-// The syntax of the function is:
-//
-//    OCT2DEC(number)
-//
-func (fn *formulaFuncs) OCT2DEC(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "OCT2DEC requires 1 numeric argument")
-	}
-	token := argsList.Front().Value.(formulaArg)
-	number := token.ToNumber()
-	if number.Type != ArgNumber {
-		return newErrorFormulaArg(formulaErrorVALUE, number.Error)
-	}
-	return fn.oct2dec(token.Value())
-}
-
-// OCT2HEX function converts an Octal (Base 8) number into a Hexadecimal
-// (Base 16) number. The syntax of the function is:
-//
-//    OCT2HEX(number,[places])
-//
-func (fn *formulaFuncs) OCT2HEX(argsList *list.List) formulaArg {
-	if argsList.Len() < 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "OCT2HEX requires at least 1 argument")
-	}
-	if argsList.Len() > 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "OCT2HEX allows at most 2 arguments")
-	}
-	token := argsList.Front().Value.(formulaArg)
-	number := token.ToNumber()
-	if number.Type != ArgNumber {
-		return newErrorFormulaArg(formulaErrorVALUE, number.Error)
-	}
-	decimal, newList := fn.oct2dec(token.Value()), list.New()
-	newList.PushBack(decimal)
-	if argsList.Len() == 2 {
-		newList.PushBack(argsList.Back().Value.(formulaArg))
-	}
-	return fn.dec2x("OCT2HEX", newList)
-}
-
-// oct2dec is an implementation of the formula function OCT2DEC.
-func (fn *formulaFuncs) oct2dec(number string) formulaArg {
-	decimal, length := 0.0, len(number)
-	for i := length; i > 0; i-- {
-		num, _ := strconv.Atoi(string(number[length-i]))
-		if i == 10 && string(number[length-i]) == "7" {
-			decimal += math.Pow(-8.0, float64(i-1))
-			continue
-		}
-		decimal += float64(num) * math.Pow(8.0, float64(i-1))
-	}
-	return newNumberFormulaArg(decimal)
-}
-
-// Math and Trigonometric Functions
-
-// ABS function returns the absolute value of any supplied number. The syntax
-// of the function is:
-//
-//    ABS(number)
-//
-func (fn *formulaFuncs) ABS(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "ABS requires 1 numeric argument")
-	}
-	arg := argsList.Front().Value.(formulaArg).ToNumber()
-	if arg.Type == ArgError {
-		return arg
-	}
-	return newNumberFormulaArg(math.Abs(arg.Number))
-}
-
-// ACOS function calculates the arccosine (i.e. the inverse cosine) of a given
-// number, and returns an angle, in radians, between 0 and π. The syntax of
-// the function is:
-//
-//    ACOS(number)
-//
-func (fn *formulaFuncs) ACOS(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "ACOS requires 1 numeric argument")
-	}
-	arg := argsList.Front().Value.(formulaArg).ToNumber()
-	if arg.Type == ArgError {
-		return arg
-	}
-	return newNumberFormulaArg(math.Acos(arg.Number))
-}
-
-// ACOSH function calculates the inverse hyperbolic cosine of a supplied number.
-// of the function is:
-//
-//    ACOSH(number)
-//
-func (fn *formulaFuncs) ACOSH(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "ACOSH requires 1 numeric argument")
-	}
-	arg := argsList.Front().Value.(formulaArg).ToNumber()
-	if arg.Type == ArgError {
-		return arg
-	}
-	return newNumberFormulaArg(math.Acosh(arg.Number))
-}
-
-// ACOT function calculates the arccotangent (i.e. the inverse cotangent) of a
-// given number, and returns an angle, in radians, between 0 and π. The syntax
-// of the function is:
-//
-//    ACOT(number)
-//
-func (fn *formulaFuncs) ACOT(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "ACOT requires 1 numeric argument")
-	}
-	arg := argsList.Front().Value.(formulaArg).ToNumber()
-	if arg.Type == ArgError {
-		return arg
-	}
-	return newNumberFormulaArg(math.Pi/2 - math.Atan(arg.Number))
-}
-
-// ACOTH function calculates the hyperbolic arccotangent (coth) of a supplied
-// value. The syntax of the function is:
-//
-//    ACOTH(number)
-//
-func (fn *formulaFuncs) ACOTH(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "ACOTH requires 1 numeric argument")
-	}
-	arg := argsList.Front().Value.(formulaArg).ToNumber()
-	if arg.Type == ArgError {
-		return arg
-	}
-	return newNumberFormulaArg(math.Atanh(1 / arg.Number))
-}
-
-// ARABIC function converts a Roman numeral into an Arabic numeral. The syntax
-// of the function is:
-//
-//    ARABIC(text)
-//
-func (fn *formulaFuncs) ARABIC(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "ARABIC requires 1 numeric argument")
-	}
-	text := argsList.Front().Value.(formulaArg).Value()
-	if len(text) > 255 {
-		return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
-	}
-	text = strings.ToUpper(text)
-	number, actualStart, index, isNegative := 0, 0, len(text)-1, false
-	startIndex, subtractNumber, currentPartValue, currentCharValue, prevCharValue := 0, 0, 0, 0, -1
-	for index >= 0 && text[index] == ' ' {
-		index--
-	}
-	for actualStart <= index && text[actualStart] == ' ' {
-		actualStart++
-	}
-	if actualStart <= index && text[actualStart] == '-' {
-		isNegative = true
-		actualStart++
-	}
-	charMap := map[rune]int{'I': 1, 'V': 5, 'X': 10, 'L': 50, 'C': 100, 'D': 500, 'M': 1000}
-	for index >= actualStart {
-		startIndex = index
-		startChar := text[startIndex]
-		index--
-		for index >= actualStart && (text[index]|' ') == startChar {
-			index--
-		}
-		currentCharValue = charMap[rune(startChar)]
-		currentPartValue = (startIndex - index) * currentCharValue
-		if currentCharValue >= prevCharValue {
-			number += currentPartValue - subtractNumber
-			prevCharValue = currentCharValue
-			subtractNumber = 0
-			continue
-		}
-		subtractNumber += currentPartValue
-	}
-	if subtractNumber != 0 {
-		number -= subtractNumber
-	}
-	if isNegative {
-		number = -number
-	}
-	return newNumberFormulaArg(float64(number))
-}
-
-// ASIN function calculates the arcsine (i.e. the inverse sine) of a given
-// number, and returns an angle, in radians, between -π/2 and π/2. The syntax
-// of the function is:
-//
-//    ASIN(number)
-//
-func (fn *formulaFuncs) ASIN(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "ASIN requires 1 numeric argument")
-	}
-	arg := argsList.Front().Value.(formulaArg).ToNumber()
-	if arg.Type == ArgError {
-		return arg
-	}
-	return newNumberFormulaArg(math.Asin(arg.Number))
-}
-
-// ASINH function calculates the inverse hyperbolic sine of a supplied number.
-// The syntax of the function is:
-//
-//    ASINH(number)
-//
-func (fn *formulaFuncs) ASINH(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "ASINH requires 1 numeric argument")
-	}
-	arg := argsList.Front().Value.(formulaArg).ToNumber()
-	if arg.Type == ArgError {
-		return arg
-	}
-	return newNumberFormulaArg(math.Asinh(arg.Number))
-}
-
-// ATAN function calculates the arctangent (i.e. the inverse tangent) of a
-// given number, and returns an angle, in radians, between -π/2 and +π/2. The
-// syntax of the function is:
-//
-//    ATAN(number)
-//
-func (fn *formulaFuncs) ATAN(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "ATAN requires 1 numeric argument")
-	}
-	arg := argsList.Front().Value.(formulaArg).ToNumber()
-	if arg.Type == ArgError {
-		return arg
-	}
-	return newNumberFormulaArg(math.Atan(arg.Number))
-}
-
-// ATANH function calculates the inverse hyperbolic tangent of a supplied
-// number. The syntax of the function is:
-//
-//    ATANH(number)
-//
-func (fn *formulaFuncs) ATANH(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "ATANH requires 1 numeric argument")
-	}
-	arg := argsList.Front().Value.(formulaArg).ToNumber()
-	if arg.Type == ArgError {
-		return arg
-	}
-	return newNumberFormulaArg(math.Atanh(arg.Number))
-}
-
-// ATAN2 function calculates the arctangent (i.e. the inverse tangent) of a
-// given set of x and y coordinates, and returns an angle, in radians, between
-// -π/2 and +π/2. The syntax of the function is:
-//
-//    ATAN2(x_num,y_num)
-//
-func (fn *formulaFuncs) ATAN2(argsList *list.List) formulaArg {
-	if argsList.Len() != 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "ATAN2 requires 2 numeric arguments")
-	}
-	x := argsList.Back().Value.(formulaArg).ToNumber()
-	if x.Type == ArgError {
-		return x
-	}
-	y := argsList.Front().Value.(formulaArg).ToNumber()
-	if y.Type == ArgError {
-		return y
-	}
-	return newNumberFormulaArg(math.Atan2(x.Number, y.Number))
-}
-
-// BASE function converts a number into a supplied base (radix), and returns a
-// text representation of the calculated value. The syntax of the function is:
-//
-//    BASE(number,radix,[min_length])
-//
-func (fn *formulaFuncs) BASE(argsList *list.List) formulaArg {
-	if argsList.Len() < 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "BASE requires at least 2 arguments")
-	}
-	if argsList.Len() > 3 {
-		return newErrorFormulaArg(formulaErrorVALUE, "BASE allows at most 3 arguments")
-	}
-	var minLength int
-	var err error
-	number := argsList.Front().Value.(formulaArg).ToNumber()
-	if number.Type == ArgError {
-		return number
-	}
-	radix := argsList.Front().Next().Value.(formulaArg).ToNumber()
-	if radix.Type == ArgError {
-		return radix
-	}
-	if int(radix.Number) < 2 || int(radix.Number) > 36 {
-		return newErrorFormulaArg(formulaErrorVALUE, "radix must be an integer >= 2 and <= 36")
-	}
-	if argsList.Len() > 2 {
-		if minLength, err = strconv.Atoi(argsList.Back().Value.(formulaArg).String); err != nil {
-			return newErrorFormulaArg(formulaErrorVALUE, err.Error())
-		}
-	}
-	result := strconv.FormatInt(int64(number.Number), int(radix.Number))
-	if len(result) < minLength {
-		result = strings.Repeat("0", minLength-len(result)) + result
-	}
-	return newStringFormulaArg(strings.ToUpper(result))
-}
-
-// CEILING function rounds a supplied number away from zero, to the nearest
-// multiple of a given number. The syntax of the function is:
-//
-//    CEILING(number,significance)
-//
-func (fn *formulaFuncs) CEILING(argsList *list.List) formulaArg {
-	if argsList.Len() == 0 {
-		return newErrorFormulaArg(formulaErrorVALUE, "CEILING requires at least 1 argument")
-	}
-	if argsList.Len() > 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "CEILING allows at most 2 arguments")
-	}
-	number, significance, res := 0.0, 1.0, 0.0
-	n := argsList.Front().Value.(formulaArg).ToNumber()
-	if n.Type == ArgError {
-		return n
-	}
-	number = n.Number
-	if number < 0 {
-		significance = -1
-	}
-	if argsList.Len() > 1 {
-		s := argsList.Back().Value.(formulaArg).ToNumber()
-		if s.Type == ArgError {
-			return s
-		}
-		significance = s.Number
-	}
-	if significance < 0 && number > 0 {
-		return newErrorFormulaArg(formulaErrorVALUE, "negative sig to CEILING invalid")
-	}
-	if argsList.Len() == 1 {
-		return newNumberFormulaArg(math.Ceil(number))
-	}
-	number, res = math.Modf(number / significance)
-	if res > 0 {
-		number++
-	}
-	return newNumberFormulaArg(number * significance)
-}
-
-// CEILINGdotMATH function rounds a supplied number up to a supplied multiple
-// of significance. The syntax of the function is:
-//
-//    CEILING.MATH(number,[significance],[mode])
-//
-func (fn *formulaFuncs) CEILINGdotMATH(argsList *list.List) formulaArg {
-	if argsList.Len() == 0 {
-		return newErrorFormulaArg(formulaErrorVALUE, "CEILING.MATH requires at least 1 argument")
-	}
-	if argsList.Len() > 3 {
-		return newErrorFormulaArg(formulaErrorVALUE, "CEILING.MATH allows at most 3 arguments")
-	}
-	number, significance, mode := 0.0, 1.0, 1.0
-	n := argsList.Front().Value.(formulaArg).ToNumber()
-	if n.Type == ArgError {
-		return n
-	}
-	number = n.Number
-	if number < 0 {
-		significance = -1
-	}
-	if argsList.Len() > 1 {
-		s := argsList.Front().Next().Value.(formulaArg).ToNumber()
-		if s.Type == ArgError {
-			return s
-		}
-		significance = s.Number
-	}
-	if argsList.Len() == 1 {
-		return newNumberFormulaArg(math.Ceil(number))
-	}
-	if argsList.Len() > 2 {
-		m := argsList.Back().Value.(formulaArg).ToNumber()
-		if m.Type == ArgError {
-			return m
-		}
-		mode = m.Number
-	}
-	val, res := math.Modf(number / significance)
-	if res != 0 {
-		if number > 0 {
-			val++
-		} else if mode < 0 {
-			val--
-		}
-	}
-	return newNumberFormulaArg(val * significance)
-}
-
-// CEILINGdotPRECISE function rounds a supplied number up (regardless of the
-// number's sign), to the nearest multiple of a given number. The syntax of
-// the function is:
-//
-//    CEILING.PRECISE(number,[significance])
-//
-func (fn *formulaFuncs) CEILINGdotPRECISE(argsList *list.List) formulaArg {
-	if argsList.Len() == 0 {
-		return newErrorFormulaArg(formulaErrorVALUE, "CEILING.PRECISE requires at least 1 argument")
-	}
-	if argsList.Len() > 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "CEILING.PRECISE allows at most 2 arguments")
-	}
-	number, significance := 0.0, 1.0
-	n := argsList.Front().Value.(formulaArg).ToNumber()
-	if n.Type == ArgError {
-		return n
-	}
-	number = n.Number
-	if number < 0 {
-		significance = -1
-	}
-	if argsList.Len() == 1 {
-		return newNumberFormulaArg(math.Ceil(number))
-	}
-	if argsList.Len() > 1 {
-		s := argsList.Back().Value.(formulaArg).ToNumber()
-		if s.Type == ArgError {
-			return s
-		}
-		significance = s.Number
-		significance = math.Abs(significance)
-		if significance == 0 {
-			return newNumberFormulaArg(significance)
-		}
-	}
-	val, res := math.Modf(number / significance)
-	if res != 0 {
-		if number > 0 {
-			val++
-		}
-	}
-	return newNumberFormulaArg(val * significance)
-}
-
-// COMBIN function calculates the number of combinations (in any order) of a
-// given number objects from a set. The syntax of the function is:
-//
-//    COMBIN(number,number_chosen)
-//
-func (fn *formulaFuncs) COMBIN(argsList *list.List) formulaArg {
-	if argsList.Len() != 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "COMBIN requires 2 argument")
-	}
-	number, chosen, val := 0.0, 0.0, 1.0
-	n := argsList.Front().Value.(formulaArg).ToNumber()
-	if n.Type == ArgError {
-		return n
-	}
-	number = n.Number
-	c := argsList.Back().Value.(formulaArg).ToNumber()
-	if c.Type == ArgError {
-		return c
-	}
-	chosen = c.Number
-	number, chosen = math.Trunc(number), math.Trunc(chosen)
-	if chosen > number {
-		return newErrorFormulaArg(formulaErrorVALUE, "COMBIN requires number >= number_chosen")
-	}
-	if chosen == number || chosen == 0 {
-		return newNumberFormulaArg(1)
-	}
-	for c := float64(1); c <= chosen; c++ {
-		val *= (number + 1 - c) / c
-	}
-	return newNumberFormulaArg(math.Ceil(val))
-}
-
-// COMBINA function calculates the number of combinations, with repetitions,
-// of a given number objects from a set. The syntax of the function is:
-//
-//    COMBINA(number,number_chosen)
-//
-func (fn *formulaFuncs) COMBINA(argsList *list.List) formulaArg {
-	if argsList.Len() != 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "COMBINA requires 2 argument")
-	}
-	var number, chosen float64
-	n := argsList.Front().Value.(formulaArg).ToNumber()
-	if n.Type == ArgError {
-		return n
-	}
-	number = n.Number
-	c := argsList.Back().Value.(formulaArg).ToNumber()
-	if c.Type == ArgError {
-		return c
-	}
-	chosen = c.Number
-	number, chosen = math.Trunc(number), math.Trunc(chosen)
-	if number < chosen {
-		return newErrorFormulaArg(formulaErrorVALUE, "COMBINA requires number > number_chosen")
-	}
-	if number == 0 {
-		return newNumberFormulaArg(number)
-	}
-	args := list.New()
-	args.PushBack(formulaArg{
-		String: fmt.Sprintf("%g", number+chosen-1),
-		Type:   ArgString,
-	})
-	args.PushBack(formulaArg{
-		String: fmt.Sprintf("%g", number-1),
-		Type:   ArgString,
-	})
-	return fn.COMBIN(args)
-}
-
-// COS function calculates the cosine of a given angle. The syntax of the
-// function is:
-//
-//    COS(number)
-//
-func (fn *formulaFuncs) COS(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "COS requires 1 numeric argument")
-	}
-	val := argsList.Front().Value.(formulaArg).ToNumber()
-	if val.Type == ArgError {
-		return val
-	}
-	return newNumberFormulaArg(math.Cos(val.Number))
-}
-
-// COSH function calculates the hyperbolic cosine (cosh) of a supplied number.
-// The syntax of the function is:
-//
-//    COSH(number)
-//
-func (fn *formulaFuncs) COSH(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "COSH requires 1 numeric argument")
-	}
-	val := argsList.Front().Value.(formulaArg).ToNumber()
-	if val.Type == ArgError {
-		return val
-	}
-	return newNumberFormulaArg(math.Cosh(val.Number))
-}
-
-// COT function calculates the cotangent of a given angle. The syntax of the
-// function is:
-//
-//    COT(number)
-//
-func (fn *formulaFuncs) COT(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "COT requires 1 numeric argument")
-	}
-	val := argsList.Front().Value.(formulaArg).ToNumber()
-	if val.Type == ArgError {
-		return val
-	}
-	if val.Number == 0 {
-		return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV)
-	}
-	return newNumberFormulaArg(1 / math.Tan(val.Number))
-}
-
-// COTH function calculates the hyperbolic cotangent (coth) of a supplied
-// angle. The syntax of the function is:
-//
-//    COTH(number)
-//
-func (fn *formulaFuncs) COTH(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "COTH requires 1 numeric argument")
-	}
-	val := argsList.Front().Value.(formulaArg).ToNumber()
-	if val.Type == ArgError {
-		return val
-	}
-	if val.Number == 0 {
-		return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV)
-	}
-	return newNumberFormulaArg((math.Exp(val.Number) + math.Exp(-val.Number)) / (math.Exp(val.Number) - math.Exp(-val.Number)))
-}
-
-// CSC function calculates the cosecant of a given angle. The syntax of the
-// function is:
-//
-//    CSC(number)
-//
-func (fn *formulaFuncs) CSC(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "CSC requires 1 numeric argument")
-	}
-	val := argsList.Front().Value.(formulaArg).ToNumber()
-	if val.Type == ArgError {
-		return val
-	}
-	if val.Number == 0 {
-		return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV)
-	}
-	return newNumberFormulaArg(1 / math.Sin(val.Number))
-}
-
-// CSCH function calculates the hyperbolic cosecant (csch) of a supplied
-// angle. The syntax of the function is:
-//
-//    CSCH(number)
-//
-func (fn *formulaFuncs) CSCH(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "CSCH requires 1 numeric argument")
-	}
-	val := argsList.Front().Value.(formulaArg).ToNumber()
-	if val.Type == ArgError {
-		return val
-	}
-	if val.Number == 0 {
-		return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV)
-	}
-	return newNumberFormulaArg(1 / math.Sinh(val.Number))
-}
-
-// DECIMAL function converts a text representation of a number in a specified
-// base, into a decimal value. The syntax of the function is:
-//
-//    DECIMAL(text,radix)
-//
-func (fn *formulaFuncs) DECIMAL(argsList *list.List) formulaArg {
-	if argsList.Len() != 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "DECIMAL requires 2 numeric arguments")
-	}
-	var text = argsList.Front().Value.(formulaArg).String
-	var radix int
-	var err error
-	radix, err = strconv.Atoi(argsList.Back().Value.(formulaArg).String)
-	if err != nil {
-		return newErrorFormulaArg(formulaErrorVALUE, err.Error())
-	}
-	if len(text) > 2 && (strings.HasPrefix(text, "0x") || strings.HasPrefix(text, "0X")) {
-		text = text[2:]
-	}
-	val, err := strconv.ParseInt(text, radix, 64)
-	if err != nil {
-		return newErrorFormulaArg(formulaErrorVALUE, err.Error())
-	}
-	return newNumberFormulaArg(float64(val))
-}
-
-// DEGREES function converts radians into degrees. The syntax of the function
-// is:
-//
-//    DEGREES(angle)
-//
-func (fn *formulaFuncs) DEGREES(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "DEGREES requires 1 numeric argument")
-	}
-	val := argsList.Front().Value.(formulaArg).ToNumber()
-	if val.Type == ArgError {
-		return val
-	}
-	if val.Number == 0 {
-		return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV)
-	}
-	return newNumberFormulaArg(180.0 / math.Pi * val.Number)
-}
-
-// EVEN function rounds a supplied number away from zero (i.e. rounds a
-// positive number up and a negative number down), to the next even number.
-// The syntax of the function is:
-//
-//    EVEN(number)
-//
-func (fn *formulaFuncs) EVEN(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "EVEN requires 1 numeric argument")
-	}
-	number := argsList.Front().Value.(formulaArg).ToNumber()
-	if number.Type == ArgError {
-		return number
-	}
-	sign := math.Signbit(number.Number)
-	m, frac := math.Modf(number.Number / 2)
-	val := m * 2
-	if frac != 0 {
-		if !sign {
-			val += 2
-		} else {
-			val -= 2
-		}
-	}
-	return newNumberFormulaArg(val)
-}
-
-// EXP function calculates the value of the mathematical constant e, raised to
-// the power of a given number. The syntax of the function is:
-//
-//    EXP(number)
-//
-func (fn *formulaFuncs) EXP(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "EXP requires 1 numeric argument")
-	}
-	number := argsList.Front().Value.(formulaArg).ToNumber()
-	if number.Type == ArgError {
-		return number
-	}
-	return newStringFormulaArg(strings.ToUpper(fmt.Sprintf("%g", math.Exp(number.Number))))
-}
-
-// fact returns the factorial of a supplied number.
-func fact(number float64) float64 {
-	val := float64(1)
-	for i := float64(2); i <= number; i++ {
-		val *= i
-	}
-	return val
-}
-
-// FACT function returns the factorial of a supplied number. The syntax of the
-// function is:
-//
-//    FACT(number)
-//
-func (fn *formulaFuncs) FACT(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "FACT requires 1 numeric argument")
-	}
-	number := argsList.Front().Value.(formulaArg).ToNumber()
-	if number.Type == ArgError {
-		return number
-	}
-	if number.Number < 0 {
-		return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
-	}
-	return newNumberFormulaArg(fact(number.Number))
-}
-
-// FACTDOUBLE function returns the double factorial of a supplied number. The
-// syntax of the function is:
-//
-//    FACTDOUBLE(number)
-//
-func (fn *formulaFuncs) FACTDOUBLE(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "FACTDOUBLE requires 1 numeric argument")
-	}
-	val := 1.0
-	number := argsList.Front().Value.(formulaArg).ToNumber()
-	if number.Type == ArgError {
-		return number
-	}
-	if number.Number < 0 {
-		return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
-	}
-	for i := math.Trunc(number.Number); i > 1; i -= 2 {
-		val *= i
-	}
-	return newStringFormulaArg(strings.ToUpper(fmt.Sprintf("%g", val)))
-}
-
-// FLOOR function rounds a supplied number towards zero to the nearest
-// multiple of a specified significance. The syntax of the function is:
-//
-//    FLOOR(number,significance)
-//
-func (fn *formulaFuncs) FLOOR(argsList *list.List) formulaArg {
-	if argsList.Len() != 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "FLOOR requires 2 numeric arguments")
-	}
-	number := argsList.Front().Value.(formulaArg).ToNumber()
-	if number.Type == ArgError {
-		return number
-	}
-	significance := argsList.Back().Value.(formulaArg).ToNumber()
-	if significance.Type == ArgError {
-		return significance
-	}
-	if significance.Number < 0 && number.Number >= 0 {
-		return newErrorFormulaArg(formulaErrorNUM, "invalid arguments to FLOOR")
-	}
-	val := number.Number
-	val, res := math.Modf(val / significance.Number)
-	if res != 0 {
-		if number.Number < 0 && res < 0 {
-			val--
-		}
-	}
-	return newStringFormulaArg(strings.ToUpper(fmt.Sprintf("%g", val*significance.Number)))
-}
-
-// FLOORdotMATH function rounds a supplied number down to a supplied multiple
-// of significance. The syntax of the function is:
-//
-//    FLOOR.MATH(number,[significance],[mode])
-//
-func (fn *formulaFuncs) FLOORdotMATH(argsList *list.List) formulaArg {
-	if argsList.Len() == 0 {
-		return newErrorFormulaArg(formulaErrorVALUE, "FLOOR.MATH requires at least 1 argument")
-	}
-	if argsList.Len() > 3 {
-		return newErrorFormulaArg(formulaErrorVALUE, "FLOOR.MATH allows at most 3 arguments")
-	}
-	significance, mode := 1.0, 1.0
-	number := argsList.Front().Value.(formulaArg).ToNumber()
-	if number.Type == ArgError {
-		return number
-	}
-	if number.Number < 0 {
-		significance = -1
-	}
-	if argsList.Len() > 1 {
-		s := argsList.Front().Next().Value.(formulaArg).ToNumber()
-		if s.Type == ArgError {
-			return s
-		}
-		significance = s.Number
-	}
-	if argsList.Len() == 1 {
-		return newNumberFormulaArg(math.Floor(number.Number))
-	}
-	if argsList.Len() > 2 {
-		m := argsList.Back().Value.(formulaArg).ToNumber()
-		if m.Type == ArgError {
-			return m
-		}
-		mode = m.Number
-	}
-	val, res := math.Modf(number.Number / significance)
-	if res != 0 && number.Number < 0 && mode > 0 {
-		val--
-	}
-	return newNumberFormulaArg(val * significance)
-}
-
-// FLOORdotPRECISE function rounds a supplied number down to a supplied
-// multiple of significance. The syntax of the function is:
-//
-//    FLOOR.PRECISE(number,[significance])
-//
-func (fn *formulaFuncs) FLOORdotPRECISE(argsList *list.List) formulaArg {
-	if argsList.Len() == 0 {
-		return newErrorFormulaArg(formulaErrorVALUE, "FLOOR.PRECISE requires at least 1 argument")
-	}
-	if argsList.Len() > 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "FLOOR.PRECISE allows at most 2 arguments")
-	}
-	var significance float64
-	number := argsList.Front().Value.(formulaArg).ToNumber()
-	if number.Type == ArgError {
-		return number
-	}
-	if number.Number < 0 {
-		significance = -1
-	}
-	if argsList.Len() == 1 {
-		return newNumberFormulaArg(math.Floor(number.Number))
-	}
-	if argsList.Len() > 1 {
-		s := argsList.Back().Value.(formulaArg).ToNumber()
-		if s.Type == ArgError {
-			return s
-		}
-		significance = s.Number
-		significance = math.Abs(significance)
-		if significance == 0 {
-			return newNumberFormulaArg(significance)
-		}
-	}
-	val, res := math.Modf(number.Number / significance)
-	if res != 0 {
-		if number.Number < 0 {
-			val--
-		}
-	}
-	return newNumberFormulaArg(val * significance)
-}
-
-// gcd returns the greatest common divisor of two supplied integers.
-func gcd(x, y float64) float64 {
-	x, y = math.Trunc(x), math.Trunc(y)
-	if x == 0 {
-		return y
-	}
-	if y == 0 {
-		return x
-	}
-	for x != y {
-		if x > y {
-			x = x - y
-		} else {
-			y = y - x
-		}
-	}
-	return x
-}
-
-// GCD function returns the greatest common divisor of two or more supplied
-// integers. The syntax of the function is:
-//
-//    GCD(number1,[number2],...)
-//
-func (fn *formulaFuncs) GCD(argsList *list.List) formulaArg {
-	if argsList.Len() == 0 {
-		return newErrorFormulaArg(formulaErrorVALUE, "GCD requires at least 1 argument")
-	}
-	var (
-		val  float64
-		nums = []float64{}
-	)
-	for arg := argsList.Front(); arg != nil; arg = arg.Next() {
-		token := arg.Value.(formulaArg)
-		switch token.Type {
-		case ArgString:
-			num := token.ToNumber()
-			if num.Type == ArgError {
-				return num
-			}
-			val = num.Number
-		case ArgNumber:
-			val = token.Number
-		}
-		nums = append(nums, val)
-	}
-	if nums[0] < 0 {
-		return newErrorFormulaArg(formulaErrorVALUE, "GCD only accepts positive arguments")
-	}
-	if len(nums) == 1 {
-		return newNumberFormulaArg(nums[0])
-	}
-	cd := nums[0]
-	for i := 1; i < len(nums); i++ {
-		if nums[i] < 0 {
-			return newErrorFormulaArg(formulaErrorVALUE, "GCD only accepts positive arguments")
-		}
-		cd = gcd(cd, nums[i])
-	}
-	return newNumberFormulaArg(cd)
-}
-
-// INT function truncates a supplied number down to the closest integer. The
-// syntax of the function is:
-//
-//    INT(number)
-//
-func (fn *formulaFuncs) INT(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "INT requires 1 numeric argument")
-	}
-	number := argsList.Front().Value.(formulaArg).ToNumber()
-	if number.Type == ArgError {
-		return number
-	}
-	val, frac := math.Modf(number.Number)
-	if frac < 0 {
-		val--
-	}
-	return newNumberFormulaArg(val)
-}
-
-// ISOdotCEILING function rounds a supplied number up (regardless of the
-// number's sign), to the nearest multiple of a supplied significance. The
-// syntax of the function is:
-//
-//    ISO.CEILING(number,[significance])
-//
-func (fn *formulaFuncs) ISOdotCEILING(argsList *list.List) formulaArg {
-	if argsList.Len() == 0 {
-		return newErrorFormulaArg(formulaErrorVALUE, "ISO.CEILING requires at least 1 argument")
-	}
-	if argsList.Len() > 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "ISO.CEILING allows at most 2 arguments")
-	}
-	var significance float64
-	number := argsList.Front().Value.(formulaArg).ToNumber()
-	if number.Type == ArgError {
-		return number
-	}
-	if number.Number < 0 {
-		significance = -1
-	}
-	if argsList.Len() == 1 {
-		return newNumberFormulaArg(math.Ceil(number.Number))
-	}
-	if argsList.Len() > 1 {
-		s := argsList.Back().Value.(formulaArg).ToNumber()
-		if s.Type == ArgError {
-			return s
-		}
-		significance = s.Number
-		significance = math.Abs(significance)
-		if significance == 0 {
-			return newNumberFormulaArg(significance)
-		}
-	}
-	val, res := math.Modf(number.Number / significance)
-	if res != 0 {
-		if number.Number > 0 {
-			val++
-		}
-	}
-	return newNumberFormulaArg(val * significance)
-}
-
-// lcm returns the least common multiple of two supplied integers.
-func lcm(a, b float64) float64 {
-	a = math.Trunc(a)
-	b = math.Trunc(b)
-	if a == 0 && b == 0 {
-		return 0
-	}
-	return a * b / gcd(a, b)
-}
-
-// LCM function returns the least common multiple of two or more supplied
-// integers. The syntax of the function is:
-//
-//    LCM(number1,[number2],...)
-//
-func (fn *formulaFuncs) LCM(argsList *list.List) formulaArg {
-	if argsList.Len() == 0 {
-		return newErrorFormulaArg(formulaErrorVALUE, "LCM requires at least 1 argument")
-	}
-	var (
-		val  float64
-		nums = []float64{}
-		err  error
-	)
-	for arg := argsList.Front(); arg != nil; arg = arg.Next() {
-		token := arg.Value.(formulaArg)
-		switch token.Type {
-		case ArgString:
-			if token.String == "" {
-				continue
-			}
-			if val, err = strconv.ParseFloat(token.String, 64); err != nil {
-				return newErrorFormulaArg(formulaErrorVALUE, err.Error())
-			}
-		case ArgNumber:
-			val = token.Number
-		}
-		nums = append(nums, val)
-	}
-	if nums[0] < 0 {
-		return newErrorFormulaArg(formulaErrorVALUE, "LCM only accepts positive arguments")
-	}
-	if len(nums) == 1 {
-		return newNumberFormulaArg(nums[0])
-	}
-	cm := nums[0]
-	for i := 1; i < len(nums); i++ {
-		if nums[i] < 0 {
-			return newErrorFormulaArg(formulaErrorVALUE, "LCM only accepts positive arguments")
-		}
-		cm = lcm(cm, nums[i])
-	}
-	return newNumberFormulaArg(cm)
-}
-
-// LN function calculates the natural logarithm of a given number. The syntax
-// of the function is:
-//
-//    LN(number)
-//
-func (fn *formulaFuncs) LN(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "LN requires 1 numeric argument")
-	}
-	number := argsList.Front().Value.(formulaArg).ToNumber()
-	if number.Type == ArgError {
-		return number
-	}
-	return newNumberFormulaArg(math.Log(number.Number))
-}
-
-// LOG function calculates the logarithm of a given number, to a supplied
-// base. The syntax of the function is:
-//
-//    LOG(number,[base])
-//
-func (fn *formulaFuncs) LOG(argsList *list.List) formulaArg {
-	if argsList.Len() == 0 {
-		return newErrorFormulaArg(formulaErrorVALUE, "LOG requires at least 1 argument")
-	}
-	if argsList.Len() > 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "LOG allows at most 2 arguments")
-	}
-	base := 10.0
-	number := argsList.Front().Value.(formulaArg).ToNumber()
-	if number.Type == ArgError {
-		return number
-	}
-	if argsList.Len() > 1 {
-		b := argsList.Back().Value.(formulaArg).ToNumber()
-		if b.Type == ArgError {
-			return b
-		}
-		base = b.Number
-	}
-	if number.Number == 0 {
-		return newErrorFormulaArg(formulaErrorNUM, formulaErrorDIV)
-	}
-	if base == 0 {
-		return newErrorFormulaArg(formulaErrorNUM, formulaErrorDIV)
-	}
-	if base == 1 {
-		return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV)
-	}
-	return newNumberFormulaArg(math.Log(number.Number) / math.Log(base))
-}
-
-// LOG10 function calculates the base 10 logarithm of a given number. The
-// syntax of the function is:
-//
-//    LOG10(number)
-//
-func (fn *formulaFuncs) LOG10(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "LOG10 requires 1 numeric argument")
-	}
-	number := argsList.Front().Value.(formulaArg).ToNumber()
-	if number.Type == ArgError {
-		return number
-	}
-	return newNumberFormulaArg(math.Log10(number.Number))
-}
-
-// minor function implement a minor of a matrix A is the determinant of some
-// smaller square matrix.
-func minor(sqMtx [][]float64, idx int) [][]float64 {
-	ret := [][]float64{}
-	for i := range sqMtx {
-		if i == 0 {
-			continue
-		}
-		row := []float64{}
-		for j := range sqMtx {
-			if j == idx {
-				continue
-			}
-			row = append(row, sqMtx[i][j])
-		}
-		ret = append(ret, row)
-	}
-	return ret
-}
-
-// det determinant of the 2x2 matrix.
-func det(sqMtx [][]float64) float64 {
-	if len(sqMtx) == 2 {
-		m00 := sqMtx[0][0]
-		m01 := sqMtx[0][1]
-		m10 := sqMtx[1][0]
-		m11 := sqMtx[1][1]
-		return m00*m11 - m10*m01
-	}
-	var res, sgn float64 = 0, 1
-	for j := range sqMtx {
-		res += sgn * sqMtx[0][j] * det(minor(sqMtx, j))
-		sgn *= -1
-	}
-	return res
-}
-
-// MDETERM calculates the determinant of a square matrix. The
-// syntax of the function is:
-//
-//    MDETERM(array)
-//
-func (fn *formulaFuncs) MDETERM(argsList *list.List) (result formulaArg) {
-	var (
-		num    float64
-		numMtx = [][]float64{}
-		err    error
-		strMtx [][]formulaArg
-	)
-	if argsList.Len() < 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "MDETERM requires at least 1 argument")
-	}
-	strMtx = argsList.Front().Value.(formulaArg).Matrix
-	var rows = len(strMtx)
-	for _, row := range argsList.Front().Value.(formulaArg).Matrix {
-		if len(row) != rows {
-			return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
-		}
-		numRow := []float64{}
-		for _, ele := range row {
-			if num, err = strconv.ParseFloat(ele.String, 64); err != nil {
-				return newErrorFormulaArg(formulaErrorVALUE, err.Error())
-			}
-			numRow = append(numRow, num)
-		}
-		numMtx = append(numMtx, numRow)
-	}
-	return newNumberFormulaArg(det(numMtx))
-}
-
-// MOD function returns the remainder of a division between two supplied
-// numbers. The syntax of the function is:
-//
-//    MOD(number,divisor)
-//
-func (fn *formulaFuncs) MOD(argsList *list.List) formulaArg {
-	if argsList.Len() != 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "MOD requires 2 numeric arguments")
-	}
-	number := argsList.Front().Value.(formulaArg).ToNumber()
-	if number.Type == ArgError {
-		return number
-	}
-	divisor := argsList.Back().Value.(formulaArg).ToNumber()
-	if divisor.Type == ArgError {
-		return divisor
-	}
-	if divisor.Number == 0 {
-		return newErrorFormulaArg(formulaErrorDIV, "MOD divide by zero")
-	}
-	trunc, rem := math.Modf(number.Number / divisor.Number)
-	if rem < 0 {
-		trunc--
-	}
-	return newNumberFormulaArg(number.Number - divisor.Number*trunc)
-}
-
-// MROUND function rounds a supplied number up or down to the nearest multiple
-// of a given number. The syntax of the function is:
-//
-//    MROUND(number,multiple)
-//
-func (fn *formulaFuncs) MROUND(argsList *list.List) formulaArg {
-	if argsList.Len() != 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "MROUND requires 2 numeric arguments")
-	}
-	n := argsList.Front().Value.(formulaArg).ToNumber()
-	if n.Type == ArgError {
-		return n
-	}
-	multiple := argsList.Back().Value.(formulaArg).ToNumber()
-	if multiple.Type == ArgError {
-		return multiple
-	}
-	if multiple.Number == 0 {
-		return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
-	}
-	if multiple.Number < 0 && n.Number > 0 ||
-		multiple.Number > 0 && n.Number < 0 {
-		return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
-	}
-	number, res := math.Modf(n.Number / multiple.Number)
-	if math.Trunc(res+0.5) > 0 {
-		number++
-	}
-	return newNumberFormulaArg(number * multiple.Number)
-}
-
-// MULTINOMIAL function calculates the ratio of the factorial of a sum of
-// supplied values to the product of factorials of those values. The syntax of
-// the function is:
-//
-//    MULTINOMIAL(number1,[number2],...)
-//
-func (fn *formulaFuncs) MULTINOMIAL(argsList *list.List) formulaArg {
-	val, num, denom := 0.0, 0.0, 1.0
-	var err error
-	for arg := argsList.Front(); arg != nil; arg = arg.Next() {
-		token := arg.Value.(formulaArg)
-		switch token.Type {
-		case ArgString:
-			if token.String == "" {
-				continue
-			}
-			if val, err = strconv.ParseFloat(token.String, 64); err != nil {
-				return newErrorFormulaArg(formulaErrorVALUE, err.Error())
-			}
-		case ArgNumber:
-			val = token.Number
-		}
-		num += val
-		denom *= fact(val)
-	}
-	return newNumberFormulaArg(fact(num) / denom)
-}
-
-// MUNIT function returns the unit matrix for a specified dimension. The
-// syntax of the function is:
-//
-//   MUNIT(dimension)
-//
-func (fn *formulaFuncs) MUNIT(argsList *list.List) (result formulaArg) {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "MUNIT requires 1 numeric argument")
-	}
-	dimension := argsList.Back().Value.(formulaArg).ToNumber()
-	if dimension.Type == ArgError || dimension.Number < 0 {
-		return newErrorFormulaArg(formulaErrorVALUE, dimension.Error)
-	}
-	matrix := make([][]formulaArg, 0, int(dimension.Number))
-	for i := 0; i < int(dimension.Number); i++ {
-		row := make([]formulaArg, int(dimension.Number))
-		for j := 0; j < int(dimension.Number); j++ {
-			if i == j {
-				row[j] = newNumberFormulaArg(1.0)
-			} else {
-				row[j] = newNumberFormulaArg(0.0)
-			}
-		}
-		matrix = append(matrix, row)
-	}
-	return newMatrixFormulaArg(matrix)
-}
-
-// ODD function ounds a supplied number away from zero (i.e. rounds a positive
-// number up and a negative number down), to the next odd number. The syntax
-// of the function is:
-//
-//   ODD(number)
-//
-func (fn *formulaFuncs) ODD(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "ODD requires 1 numeric argument")
-	}
-	number := argsList.Back().Value.(formulaArg).ToNumber()
-	if number.Type == ArgError {
-		return number
-	}
-	if number.Number == 0 {
-		return newNumberFormulaArg(1)
-	}
-	sign := math.Signbit(number.Number)
-	m, frac := math.Modf((number.Number - 1) / 2)
-	val := m*2 + 1
-	if frac != 0 {
-		if !sign {
-			val += 2
-		} else {
-			val -= 2
-		}
-	}
-	return newNumberFormulaArg(val)
-}
-
-// PI function returns the value of the mathematical constant π (pi), accurate
-// to 15 digits (14 decimal places). The syntax of the function is:
-//
-//   PI()
-//
-func (fn *formulaFuncs) PI(argsList *list.List) formulaArg {
-	if argsList.Len() != 0 {
-		return newErrorFormulaArg(formulaErrorVALUE, "PI accepts no arguments")
-	}
-	return newNumberFormulaArg(math.Pi)
-}
-
-// POWER function calculates a given number, raised to a supplied power.
-// The syntax of the function is:
-//
-//    POWER(number,power)
-//
-func (fn *formulaFuncs) POWER(argsList *list.List) formulaArg {
-	if argsList.Len() != 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "POWER requires 2 numeric arguments")
-	}
-	x := argsList.Front().Value.(formulaArg).ToNumber()
-	if x.Type == ArgError {
-		return x
-	}
-	y := argsList.Back().Value.(formulaArg).ToNumber()
-	if y.Type == ArgError {
-		return y
-	}
-	if x.Number == 0 && y.Number == 0 {
-		return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
-	}
-	if x.Number == 0 && y.Number < 0 {
-		return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV)
-	}
-	return newNumberFormulaArg(math.Pow(x.Number, y.Number))
-}
-
-// PRODUCT function returns the product (multiplication) of a supplied set of
-// numerical values. The syntax of the function is:
-//
-//    PRODUCT(number1,[number2],...)
-//
-func (fn *formulaFuncs) PRODUCT(argsList *list.List) formulaArg {
-	val, product := 0.0, 1.0
-	var err error
-	for arg := argsList.Front(); arg != nil; arg = arg.Next() {
-		token := arg.Value.(formulaArg)
-		switch token.Type {
-		case ArgUnknown:
-			continue
-		case ArgString:
-			if token.String == "" {
-				continue
-			}
-			if val, err = strconv.ParseFloat(token.String, 64); err != nil {
-				return newErrorFormulaArg(formulaErrorVALUE, err.Error())
-			}
-			product = product * val
-		case ArgNumber:
-			product = product * token.Number
-		case ArgMatrix:
-			for _, row := range token.Matrix {
-				for _, value := range row {
-					if value.String == "" {
-						continue
-					}
-					if val, err = strconv.ParseFloat(value.String, 64); err != nil {
-						return newErrorFormulaArg(formulaErrorVALUE, err.Error())
-					}
-					product = product * val
-				}
-			}
-		}
-	}
-	return newNumberFormulaArg(product)
-}
-
-// QUOTIENT function returns the integer portion of a division between two
-// supplied numbers. The syntax of the function is:
-//
-//   QUOTIENT(numerator,denominator)
-//
-func (fn *formulaFuncs) QUOTIENT(argsList *list.List) formulaArg {
-	if argsList.Len() != 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "QUOTIENT requires 2 numeric arguments")
-	}
-	x := argsList.Front().Value.(formulaArg).ToNumber()
-	if x.Type == ArgError {
-		return x
-	}
-	y := argsList.Back().Value.(formulaArg).ToNumber()
-	if y.Type == ArgError {
-		return y
-	}
-	if y.Number == 0 {
-		return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV)
-	}
-	return newNumberFormulaArg(math.Trunc(x.Number / y.Number))
-}
-
-// RADIANS function converts radians into degrees. The syntax of the function is:
-//
-//   RADIANS(angle)
-//
-func (fn *formulaFuncs) RADIANS(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "RADIANS requires 1 numeric argument")
-	}
-	angle := argsList.Front().Value.(formulaArg).ToNumber()
-	if angle.Type == ArgError {
-		return angle
-	}
-	return newNumberFormulaArg(math.Pi / 180.0 * angle.Number)
-}
-
-// RAND function generates a random real number between 0 and 1. The syntax of
-// the function is:
-//
-//   RAND()
-//
-func (fn *formulaFuncs) RAND(argsList *list.List) formulaArg {
-	if argsList.Len() != 0 {
-		return newErrorFormulaArg(formulaErrorVALUE, "RAND accepts no arguments")
-	}
-	return newNumberFormulaArg(rand.New(rand.NewSource(time.Now().UnixNano())).Float64())
-}
-
-// RANDBETWEEN function generates a random integer between two supplied
-// integers. The syntax of the function is:
-//
-//   RANDBETWEEN(bottom,top)
-//
-func (fn *formulaFuncs) RANDBETWEEN(argsList *list.List) formulaArg {
-	if argsList.Len() != 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "RANDBETWEEN requires 2 numeric arguments")
-	}
-	bottom := argsList.Front().Value.(formulaArg).ToNumber()
-	if bottom.Type == ArgError {
-		return bottom
-	}
-	top := argsList.Back().Value.(formulaArg).ToNumber()
-	if top.Type == ArgError {
-		return top
-	}
-	if top.Number < bottom.Number {
-		return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
-	}
-	num := rand.New(rand.NewSource(time.Now().UnixNano())).Int63n(int64(top.Number - bottom.Number + 1))
-	return newNumberFormulaArg(float64(num + int64(bottom.Number)))
-}
-
-// romanNumerals defined a numeral system that originated in ancient Rome and
-// remained the usual way of writing numbers throughout Europe well into the
-// Late Middle Ages.
-type romanNumerals struct {
-	n float64
-	s string
-}
-
-var romanTable = [][]romanNumerals{
-	{
-		{1000, "M"}, {900, "CM"}, {500, "D"}, {400, "CD"}, {100, "C"}, {90, "XC"},
-		{50, "L"}, {40, "XL"}, {10, "X"}, {9, "IX"}, {5, "V"}, {4, "IV"}, {1, "I"},
-	},
-	{
-		{1000, "M"}, {950, "LM"}, {900, "CM"}, {500, "D"}, {450, "LD"}, {400, "CD"},
-		{100, "C"}, {95, "VC"}, {90, "XC"}, {50, "L"}, {45, "VL"}, {40, "XL"},
-		{10, "X"}, {9, "IX"}, {5, "V"}, {4, "IV"}, {1, "I"},
-	},
-	{
-		{1000, "M"}, {990, "XM"}, {950, "LM"}, {900, "CM"}, {500, "D"}, {490, "XD"},
-		{450, "LD"}, {400, "CD"}, {100, "C"}, {99, "IC"}, {90, "XC"}, {50, "L"},
-		{45, "VL"}, {40, "XL"}, {10, "X"}, {9, "IX"}, {5, "V"}, {4, "IV"}, {1, "I"},
-	},
-	{
-		{1000, "M"}, {995, "VM"}, {990, "XM"}, {950, "LM"}, {900, "CM"}, {500, "D"},
-		{495, "VD"}, {490, "XD"}, {450, "LD"}, {400, "CD"}, {100, "C"}, {99, "IC"},
-		{90, "XC"}, {50, "L"}, {45, "VL"}, {40, "XL"}, {10, "X"}, {9, "IX"},
-		{5, "V"}, {4, "IV"}, {1, "I"},
-	},
-	{
-		{1000, "M"}, {999, "IM"}, {995, "VM"}, {990, "XM"}, {950, "LM"}, {900, "CM"},
-		{500, "D"}, {499, "ID"}, {495, "VD"}, {490, "XD"}, {450, "LD"}, {400, "CD"},
-		{100, "C"}, {99, "IC"}, {90, "XC"}, {50, "L"}, {45, "VL"}, {40, "XL"},
-		{10, "X"}, {9, "IX"}, {5, "V"}, {4, "IV"}, {1, "I"},
-	},
-}
-
-// ROMAN function converts an arabic number to Roman. I.e. for a supplied
-// integer, the function returns a text string depicting the roman numeral
-// form of the number. The syntax of the function is:
-//
-//   ROMAN(number,[form])
-//
-func (fn *formulaFuncs) ROMAN(argsList *list.List) formulaArg {
-	if argsList.Len() == 0 {
-		return newErrorFormulaArg(formulaErrorVALUE, "ROMAN requires at least 1 argument")
-	}
-	if argsList.Len() > 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "ROMAN allows at most 2 arguments")
-	}
-	var form int
-	number := argsList.Front().Value.(formulaArg).ToNumber()
-	if number.Type == ArgError {
-		return number
-	}
-	if argsList.Len() > 1 {
-		f := argsList.Back().Value.(formulaArg).ToNumber()
-		if f.Type == ArgError {
-			return f
-		}
-		form = int(f.Number)
-		if form < 0 {
-			form = 0
-		} else if form > 4 {
-			form = 4
-		}
-	}
-	decimalTable := romanTable[0]
-	switch form {
-	case 1:
-		decimalTable = romanTable[1]
-	case 2:
-		decimalTable = romanTable[2]
-	case 3:
-		decimalTable = romanTable[3]
-	case 4:
-		decimalTable = romanTable[4]
-	}
-	val := math.Trunc(number.Number)
-	buf := bytes.Buffer{}
-	for _, r := range decimalTable {
-		for val >= r.n {
-			buf.WriteString(r.s)
-			val -= r.n
-		}
-	}
-	return newStringFormulaArg(buf.String())
-}
-
-type roundMode byte
-
-const (
-	closest roundMode = iota
-	down
-	up
-)
-
-// round rounds a supplied number up or down.
-func (fn *formulaFuncs) round(number, digits float64, mode roundMode) float64 {
-	var significance float64
-	if digits > 0 {
-		significance = math.Pow(1/10.0, digits)
-	} else {
-		significance = math.Pow(10.0, -digits)
-	}
-	val, res := math.Modf(number / significance)
-	switch mode {
-	case closest:
-		const eps = 0.499999999
-		if res >= eps {
-			val++
-		} else if res <= -eps {
-			val--
-		}
-	case down:
-	case up:
-		if res > 0 {
-			val++
-		} else if res < 0 {
-			val--
-		}
-	}
-	return val * significance
-}
-
-// ROUND function rounds a supplied number up or down, to a specified number
-// of decimal places. The syntax of the function is:
-//
-//   ROUND(number,num_digits)
-//
-func (fn *formulaFuncs) ROUND(argsList *list.List) formulaArg {
-	if argsList.Len() != 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "ROUND requires 2 numeric arguments")
-	}
-	number := argsList.Front().Value.(formulaArg).ToNumber()
-	if number.Type == ArgError {
-		return number
-	}
-	digits := argsList.Back().Value.(formulaArg).ToNumber()
-	if digits.Type == ArgError {
-		return digits
-	}
-	return newNumberFormulaArg(fn.round(number.Number, digits.Number, closest))
-}
-
-// ROUNDDOWN function rounds a supplied number down towards zero, to a
-// specified number of decimal places. The syntax of the function is:
-//
-//   ROUNDDOWN(number,num_digits)
-//
-func (fn *formulaFuncs) ROUNDDOWN(argsList *list.List) formulaArg {
-	if argsList.Len() != 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "ROUNDDOWN requires 2 numeric arguments")
-	}
-	number := argsList.Front().Value.(formulaArg).ToNumber()
-	if number.Type == ArgError {
-		return number
-	}
-	digits := argsList.Back().Value.(formulaArg).ToNumber()
-	if digits.Type == ArgError {
-		return digits
-	}
-	return newNumberFormulaArg(fn.round(number.Number, digits.Number, down))
-}
-
-// ROUNDUP function rounds a supplied number up, away from zero, to a
-// specified number of decimal places. The syntax of the function is:
-//
-//   ROUNDUP(number,num_digits)
-//
-func (fn *formulaFuncs) ROUNDUP(argsList *list.List) formulaArg {
-	if argsList.Len() != 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "ROUNDUP requires 2 numeric arguments")
-	}
-	number := argsList.Front().Value.(formulaArg).ToNumber()
-	if number.Type == ArgError {
-		return number
-	}
-	digits := argsList.Back().Value.(formulaArg).ToNumber()
-	if digits.Type == ArgError {
-		return digits
-	}
-	return newNumberFormulaArg(fn.round(number.Number, digits.Number, up))
-}
-
-// SEC function calculates the secant of a given angle. The syntax of the
-// function is:
-//
-//    SEC(number)
-//
-func (fn *formulaFuncs) SEC(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "SEC requires 1 numeric argument")
-	}
-	number := argsList.Front().Value.(formulaArg).ToNumber()
-	if number.Type == ArgError {
-		return number
-	}
-	return newNumberFormulaArg(math.Cos(number.Number))
-}
-
-// SECH function calculates the hyperbolic secant (sech) of a supplied angle.
-// The syntax of the function is:
-//
-//    SECH(number)
-//
-func (fn *formulaFuncs) SECH(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "SECH requires 1 numeric argument")
-	}
-	number := argsList.Front().Value.(formulaArg).ToNumber()
-	if number.Type == ArgError {
-		return number
-	}
-	return newNumberFormulaArg(1 / math.Cosh(number.Number))
-}
-
-// SIGN function returns the arithmetic sign (+1, -1 or 0) of a supplied
-// number. I.e. if the number is positive, the Sign function returns +1, if
-// the number is negative, the function returns -1 and if the number is 0
-// (zero), the function returns 0. The syntax of the function is:
-//
-//   SIGN(number)
-//
-func (fn *formulaFuncs) SIGN(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "SIGN requires 1 numeric argument")
-	}
-	val := argsList.Front().Value.(formulaArg).ToNumber()
-	if val.Type == ArgError {
-		return val
-	}
-	if val.Number < 0 {
-		return newNumberFormulaArg(-1)
-	}
-	if val.Number > 0 {
-		return newNumberFormulaArg(1)
-	}
-	return newNumberFormulaArg(0)
-}
-
-// SIN function calculates the sine of a given angle. The syntax of the
-// function is:
-//
-//    SIN(number)
-//
-func (fn *formulaFuncs) SIN(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "SIN requires 1 numeric argument")
-	}
-	number := argsList.Front().Value.(formulaArg).ToNumber()
-	if number.Type == ArgError {
-		return number
-	}
-	return newNumberFormulaArg(math.Sin(number.Number))
-}
-
-// SINH function calculates the hyperbolic sine (sinh) of a supplied number.
-// The syntax of the function is:
-//
-//    SINH(number)
-//
-func (fn *formulaFuncs) SINH(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "SINH requires 1 numeric argument")
-	}
-	number := argsList.Front().Value.(formulaArg).ToNumber()
-	if number.Type == ArgError {
-		return number
-	}
-	return newNumberFormulaArg(math.Sinh(number.Number))
-}
-
-// SQRT function calculates the positive square root of a supplied number. The
-// syntax of the function is:
-//
-//    SQRT(number)
-//
-func (fn *formulaFuncs) SQRT(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "SQRT requires 1 numeric argument")
-	}
-	value := argsList.Front().Value.(formulaArg).ToNumber()
-	if value.Type == ArgError {
-		return value
-	}
-	if value.Number < 0 {
-		return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
-	}
-	return newNumberFormulaArg(math.Sqrt(value.Number))
-}
-
-// SQRTPI function returns the square root of a supplied number multiplied by
-// the mathematical constant, π. The syntax of the function is:
-//
-//    SQRTPI(number)
-//
-func (fn *formulaFuncs) SQRTPI(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "SQRTPI requires 1 numeric argument")
-	}
-	number := argsList.Front().Value.(formulaArg).ToNumber()
-	if number.Type == ArgError {
-		return number
-	}
-	return newNumberFormulaArg(math.Sqrt(number.Number * math.Pi))
-}
-
-// STDEV function calculates the sample standard deviation of a supplied set
-// of values. The syntax of the function is:
-//
-//    STDEV(number1,[number2],...)
-//
-func (fn *formulaFuncs) STDEV(argsList *list.List) formulaArg {
-	if argsList.Len() < 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "STDEV requires at least 1 argument")
-	}
-	return fn.stdev(false, argsList)
-}
-
-// STDEVdotS function calculates the sample standard deviation of a supplied
-// set of values. The syntax of the function is:
-//
-//    STDEV.S(number1,[number2],...)
-//
-func (fn *formulaFuncs) STDEVdotS(argsList *list.List) formulaArg {
-	if argsList.Len() < 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "STDEV.S requires at least 1 argument")
-	}
-	return fn.stdev(false, argsList)
-}
-
-// STDEVA function estimates standard deviation based on a sample. The
-// standard deviation is a measure of how widely values are dispersed from
-// the average value (the mean). The syntax of the function is:
-//
-//    STDEVA(number1,[number2],...)
-//
-func (fn *formulaFuncs) STDEVA(argsList *list.List) formulaArg {
-	if argsList.Len() < 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "STDEVA requires at least 1 argument")
-	}
-	return fn.stdev(true, argsList)
-}
-
-// stdev is an implementation of the formula function STDEV and STDEVA.
-func (fn *formulaFuncs) stdev(stdeva bool, argsList *list.List) formulaArg {
-	pow := func(result, count float64, n, m formulaArg) (float64, float64) {
-		if result == -1 {
-			result = math.Pow((n.Number - m.Number), 2)
-		} else {
-			result += math.Pow((n.Number - m.Number), 2)
-		}
-		count++
-		return result, count
-	}
-	count, result := -1.0, -1.0
-	var mean formulaArg
-	if stdeva {
-		mean = fn.AVERAGEA(argsList)
-	} else {
-		mean = fn.AVERAGE(argsList)
-	}
-	for arg := argsList.Front(); arg != nil; arg = arg.Next() {
-		token := arg.Value.(formulaArg)
-		switch token.Type {
-		case ArgString, ArgNumber:
-			if !stdeva && (token.Value() == "TRUE" || token.Value() == "FALSE") {
-				continue
-			} else if stdeva && (token.Value() == "TRUE" || token.Value() == "FALSE") {
-				num := token.ToBool()
-				if num.Type == ArgNumber {
-					result, count = pow(result, count, num, mean)
-					continue
-				}
-			} else {
-				num := token.ToNumber()
-				if num.Type == ArgNumber {
-					result, count = pow(result, count, num, mean)
-				}
-			}
-		case ArgList, ArgMatrix:
-			for _, row := range token.ToList() {
-				if row.Type == ArgNumber || row.Type == ArgString {
-					if !stdeva && (row.Value() == "TRUE" || row.Value() == "FALSE") {
-						continue
-					} else if stdeva && (row.Value() == "TRUE" || row.Value() == "FALSE") {
-						num := row.ToBool()
-						if num.Type == ArgNumber {
-							result, count = pow(result, count, num, mean)
-							continue
-						}
-					} else {
-						num := row.ToNumber()
-						if num.Type == ArgNumber {
-							result, count = pow(result, count, num, mean)
-						}
-					}
-				}
-			}
-		}
-	}
-	if count > 0 && result >= 0 {
-		return newNumberFormulaArg(math.Sqrt(result / count))
-	}
-	return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV)
-}
-
-// POISSONdotDIST function calculates the Poisson Probability Mass Function or
-// the Cumulative Poisson Probability Function for a supplied set of
-// parameters. The syntax of the function is:
-//
-//    POISSON.DIST(x,mean,cumulative)
-//
-func (fn *formulaFuncs) POISSONdotDIST(argsList *list.List) formulaArg {
-	if argsList.Len() != 3 {
-		return newErrorFormulaArg(formulaErrorVALUE, "POISSON.DIST requires 3 arguments")
-	}
-	return fn.POISSON(argsList)
-}
-
-// POISSON function calculates the Poisson Probability Mass Function or the
-// Cumulative Poisson Probability Function for a supplied set of parameters.
-// The syntax of the function is:
-//
-//    POISSON(x,mean,cumulative)
-//
-func (fn *formulaFuncs) POISSON(argsList *list.List) formulaArg {
-	if argsList.Len() != 3 {
-		return newErrorFormulaArg(formulaErrorVALUE, "POISSON requires 3 arguments")
-	}
-	var x, mean, cumulative formulaArg
-	if x = argsList.Front().Value.(formulaArg).ToNumber(); x.Type != ArgNumber {
-		return x
-	}
-	if mean = argsList.Front().Next().Value.(formulaArg).ToNumber(); mean.Type != ArgNumber {
-		return mean
-	}
-	if cumulative = argsList.Back().Value.(formulaArg).ToBool(); cumulative.Type == ArgError {
-		return cumulative
-	}
-	if x.Number < 0 || mean.Number <= 0 {
-		return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
-	}
-	if cumulative.Number == 1 {
-		summer := 0.0
-		floor := math.Floor(x.Number)
-		for i := 0; i <= int(floor); i++ {
-			summer += math.Pow(mean.Number, float64(i)) / fact(float64(i))
-		}
-		return newNumberFormulaArg(math.Exp(0-mean.Number) * summer)
-	}
-	return newNumberFormulaArg(math.Exp(0-mean.Number) * math.Pow(mean.Number, x.Number) / fact(x.Number))
-}
-
-// SUM function adds together a supplied set of numbers and returns the sum of
-// these values. The syntax of the function is:
-//
-//    SUM(number1,[number2],...)
-//
-func (fn *formulaFuncs) SUM(argsList *list.List) formulaArg {
-	var sum float64
-	for arg := argsList.Front(); arg != nil; arg = arg.Next() {
-		token := arg.Value.(formulaArg)
-		switch token.Type {
-		case ArgUnknown:
-			continue
-		case ArgString:
-			if num := token.ToNumber(); num.Type == ArgNumber {
-				sum += num.Number
-			}
-		case ArgNumber:
-			sum += token.Number
-		case ArgMatrix:
-			for _, row := range token.Matrix {
-				for _, value := range row {
-					if num := value.ToNumber(); num.Type == ArgNumber {
-						sum += num.Number
-					}
-				}
-			}
-		}
-	}
-	return newNumberFormulaArg(sum)
-}
-
-// SUMIF function finds the values in a supplied array, that satisfy a given
-// criteria, and returns the sum of the corresponding values in a second
-// supplied array. The syntax of the function is:
-//
-//    SUMIF(range,criteria,[sum_range])
-//
-func (fn *formulaFuncs) SUMIF(argsList *list.List) formulaArg {
-	if argsList.Len() < 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "SUMIF requires at least 2 argument")
-	}
-	var criteria = formulaCriteriaParser(argsList.Front().Next().Value.(formulaArg).String)
-	var rangeMtx = argsList.Front().Value.(formulaArg).Matrix
-	var sumRange [][]formulaArg
-	if argsList.Len() == 3 {
-		sumRange = argsList.Back().Value.(formulaArg).Matrix
-	}
-	var sum, val float64
-	var err error
-	for rowIdx, row := range rangeMtx {
-		for colIdx, col := range row {
-			var ok bool
-			fromVal := col.String
-			if col.String == "" {
-				continue
-			}
-			if ok, err = formulaCriteriaEval(fromVal, criteria); err != nil {
-				return newErrorFormulaArg(formulaErrorVALUE, err.Error())
-			}
-			if ok {
-				if argsList.Len() == 3 {
-					if len(sumRange) <= rowIdx || len(sumRange[rowIdx]) <= colIdx {
-						continue
-					}
-					fromVal = sumRange[rowIdx][colIdx].String
-				}
-				if val, err = strconv.ParseFloat(fromVal, 64); err != nil {
-					return newErrorFormulaArg(formulaErrorVALUE, err.Error())
-				}
-				sum += val
-			}
-		}
-	}
-	return newNumberFormulaArg(sum)
-}
-
-// SUMSQ function returns the sum of squares of a supplied set of values. The
-// syntax of the function is:
-//
-//    SUMSQ(number1,[number2],...)
-//
-func (fn *formulaFuncs) SUMSQ(argsList *list.List) formulaArg {
-	var val, sq float64
-	var err error
-	for arg := argsList.Front(); arg != nil; arg = arg.Next() {
-		token := arg.Value.(formulaArg)
-		switch token.Type {
-		case ArgString:
-			if token.String == "" {
-				continue
-			}
-			if val, err = strconv.ParseFloat(token.String, 64); err != nil {
-				return newErrorFormulaArg(formulaErrorVALUE, err.Error())
-			}
-			sq += val * val
-		case ArgNumber:
-			sq += token.Number
-		case ArgMatrix:
-			for _, row := range token.Matrix {
-				for _, value := range row {
-					if value.String == "" {
-						continue
-					}
-					if val, err = strconv.ParseFloat(value.String, 64); err != nil {
-						return newErrorFormulaArg(formulaErrorVALUE, err.Error())
-					}
-					sq += val * val
-				}
-			}
-		}
-	}
-	return newNumberFormulaArg(sq)
-}
-
-// TAN function calculates the tangent of a given angle. The syntax of the
-// function is:
-//
-//    TAN(number)
-//
-func (fn *formulaFuncs) TAN(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "TAN requires 1 numeric argument")
-	}
-	number := argsList.Front().Value.(formulaArg).ToNumber()
-	if number.Type == ArgError {
-		return number
-	}
-	return newNumberFormulaArg(math.Tan(number.Number))
-}
-
-// TANH function calculates the hyperbolic tangent (tanh) of a supplied
-// number. The syntax of the function is:
-//
-//    TANH(number)
-//
-func (fn *formulaFuncs) TANH(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "TANH requires 1 numeric argument")
-	}
-	number := argsList.Front().Value.(formulaArg).ToNumber()
-	if number.Type == ArgError {
-		return number
-	}
-	return newNumberFormulaArg(math.Tanh(number.Number))
-}
-
-// TRUNC function truncates a supplied number to a specified number of decimal
-// places. The syntax of the function is:
-//
-//    TRUNC(number,[number_digits])
-//
-func (fn *formulaFuncs) TRUNC(argsList *list.List) formulaArg {
-	if argsList.Len() == 0 {
-		return newErrorFormulaArg(formulaErrorVALUE, "TRUNC requires at least 1 argument")
-	}
-	var digits, adjust, rtrim float64
-	var err error
-	number := argsList.Front().Value.(formulaArg).ToNumber()
-	if number.Type == ArgError {
-		return number
-	}
-	if argsList.Len() > 1 {
-		d := argsList.Back().Value.(formulaArg).ToNumber()
-		if d.Type == ArgError {
-			return d
-		}
-		digits = d.Number
-		digits = math.Floor(digits)
-	}
-	adjust = math.Pow(10, digits)
-	x := int((math.Abs(number.Number) - math.Abs(float64(int(number.Number)))) * adjust)
-	if x != 0 {
-		if rtrim, err = strconv.ParseFloat(strings.TrimRight(strconv.Itoa(x), "0"), 64); err != nil {
-			return newErrorFormulaArg(formulaErrorVALUE, err.Error())
-		}
-	}
-	if (digits > 0) && (rtrim < adjust/10) {
-		return newNumberFormulaArg(number.Number)
-	}
-	return newNumberFormulaArg(float64(int(number.Number*adjust)) / adjust)
-}
-
-// Statistical Functions
-
-// AVERAGE function returns the arithmetic mean of a list of supplied numbers.
-// The syntax of the function is:
-//
-//    AVERAGE(number1,[number2],...)
-//
-func (fn *formulaFuncs) AVERAGE(argsList *list.List) formulaArg {
-	args := []formulaArg{}
-	for arg := argsList.Front(); arg != nil; arg = arg.Next() {
-		args = append(args, arg.Value.(formulaArg))
-	}
-	count, sum := fn.countSum(false, args)
-	if count == 0 {
-		return newErrorFormulaArg(formulaErrorDIV, "AVERAGE divide by zero")
-	}
-	return newNumberFormulaArg(sum / count)
-}
-
-// AVERAGEA function returns the arithmetic mean of a list of supplied numbers
-// with text cell and zero values. The syntax of the function is:
-//
-//    AVERAGEA(number1,[number2],...)
-//
-func (fn *formulaFuncs) AVERAGEA(argsList *list.List) formulaArg {
-	args := []formulaArg{}
-	for arg := argsList.Front(); arg != nil; arg = arg.Next() {
-		args = append(args, arg.Value.(formulaArg))
-	}
-	count, sum := fn.countSum(true, args)
-	if count == 0 {
-		return newErrorFormulaArg(formulaErrorDIV, "AVERAGEA divide by zero")
-	}
-	return newNumberFormulaArg(sum / count)
-}
-
-// countSum get count and sum for a formula arguments array.
-func (fn *formulaFuncs) countSum(countText bool, args []formulaArg) (count, sum float64) {
-	for _, arg := range args {
-		switch arg.Type {
-		case ArgNumber:
-			if countText || !arg.Boolean {
-				sum += arg.Number
-				count++
-			}
-		case ArgString:
-			if !countText && (arg.Value() == "TRUE" || arg.Value() == "FALSE") {
-				continue
-			} else if countText && (arg.Value() == "TRUE" || arg.Value() == "FALSE") {
-				num := arg.ToBool()
-				if num.Type == ArgNumber {
-					count++
-					sum += num.Number
-					continue
-				}
-			}
-			num := arg.ToNumber()
-			if countText && num.Type == ArgError && arg.String != "" {
-				count++
-			}
-			if num.Type == ArgNumber {
-				sum += num.Number
-				count++
-			}
-		case ArgList, ArgMatrix:
-			cnt, summary := fn.countSum(countText, arg.ToList())
-			sum += summary
-			count += cnt
-		}
-	}
-	return
-}
-
-// COUNT function returns the count of numeric values in a supplied set of
-// cells or values. This count includes both numbers and dates. The syntax of
-// the function is:
-//
-//    COUNT(value1,[value2],...)
-//
-func (fn *formulaFuncs) COUNT(argsList *list.List) formulaArg {
-	var count int
-	for token := argsList.Front(); token != nil; token = token.Next() {
-		arg := token.Value.(formulaArg)
-		switch arg.Type {
-		case ArgString:
-			if arg.ToNumber().Type != ArgError {
-				count++
-			}
-		case ArgNumber:
-			count++
-		case ArgMatrix:
-			for _, row := range arg.Matrix {
-				for _, value := range row {
-					if value.ToNumber().Type != ArgError {
-						count++
-					}
-				}
-			}
-		}
-	}
-	return newNumberFormulaArg(float64(count))
-}
-
-// COUNTA function returns the number of non-blanks within a supplied set of
-// cells or values. The syntax of the function is:
-//
-//    COUNTA(value1,[value2],...)
-//
-func (fn *formulaFuncs) COUNTA(argsList *list.List) formulaArg {
-	var count int
-	for token := argsList.Front(); token != nil; token = token.Next() {
-		arg := token.Value.(formulaArg)
-		switch arg.Type {
-		case ArgString:
-			if arg.String != "" {
-				count++
-			}
-		case ArgNumber:
-			count++
-		case ArgMatrix:
-			for _, row := range arg.ToList() {
-				switch row.Type {
-				case ArgString:
-					if row.String != "" {
-						count++
-					}
-				case ArgNumber:
-					count++
-				}
-			}
-		}
-	}
-	return newNumberFormulaArg(float64(count))
-}
-
-// COUNTBLANK function returns the number of blank cells in a supplied range.
-// The syntax of the function is:
-//
-//    COUNTBLANK(range)
-//
-func (fn *formulaFuncs) COUNTBLANK(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "COUNTBLANK requires 1 argument")
-	}
-	var count int
-	token := argsList.Front().Value.(formulaArg)
-	switch token.Type {
-	case ArgString:
-		if token.String == "" {
-			count++
-		}
-	case ArgList, ArgMatrix:
-		for _, row := range token.ToList() {
-			switch row.Type {
-			case ArgString:
-				if row.String == "" {
-					count++
-				}
-			case ArgEmpty:
-				count++
-			}
-		}
-	case ArgEmpty:
-		count++
-	}
-	return newNumberFormulaArg(float64(count))
-}
-
-// FISHER function calculates the Fisher Transformation for a supplied value.
-// The syntax of the function is:
-//
-//    FISHER(x)
-//
-func (fn *formulaFuncs) FISHER(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "FISHER requires 1 numeric argument")
-	}
-	token := argsList.Front().Value.(formulaArg)
-	switch token.Type {
-	case ArgString:
-		arg := token.ToNumber()
-		if arg.Type == ArgNumber {
-			if arg.Number <= -1 || arg.Number >= 1 {
-				return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
-			}
-			return newNumberFormulaArg(0.5 * math.Log((1+arg.Number)/(1-arg.Number)))
-		}
-	case ArgNumber:
-		if token.Number <= -1 || token.Number >= 1 {
-			return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
-		}
-		return newNumberFormulaArg(0.5 * math.Log((1+token.Number)/(1-token.Number)))
-	}
-	return newErrorFormulaArg(formulaErrorVALUE, "FISHER requires 1 numeric argument")
-}
-
-// FISHERINV function calculates the inverse of the Fisher Transformation and
-// returns a value between -1 and +1. The syntax of the function is:
-//
-//    FISHERINV(y)
-//
-func (fn *formulaFuncs) FISHERINV(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "FISHERINV requires 1 numeric argument")
-	}
-	token := argsList.Front().Value.(formulaArg)
-	switch token.Type {
-	case ArgString:
-		arg := token.ToNumber()
-		if arg.Type == ArgNumber {
-			return newNumberFormulaArg((math.Exp(2*arg.Number) - 1) / (math.Exp(2*arg.Number) + 1))
-		}
-	case ArgNumber:
-		return newNumberFormulaArg((math.Exp(2*token.Number) - 1) / (math.Exp(2*token.Number) + 1))
-	}
-	return newErrorFormulaArg(formulaErrorVALUE, "FISHERINV requires 1 numeric argument")
-}
-
-// GAMMA function returns the value of the Gamma Function, Γ(n), for a
-// specified number, n. The syntax of the function is:
-//
-//    GAMMA(number)
-//
-func (fn *formulaFuncs) GAMMA(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "GAMMA requires 1 numeric argument")
-	}
-	token := argsList.Front().Value.(formulaArg)
-	switch token.Type {
-	case ArgString:
-		arg := token.ToNumber()
-		if arg.Type == ArgNumber {
-			if arg.Number <= 0 {
-				return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
-			}
-			return newNumberFormulaArg(math.Gamma(arg.Number))
-		}
-	case ArgNumber:
-		if token.Number <= 0 {
-			return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
-		}
-		return newNumberFormulaArg(math.Gamma(token.Number))
-	}
-	return newErrorFormulaArg(formulaErrorVALUE, "GAMMA requires 1 numeric argument")
-}
-
-// GAMMALN function returns the natural logarithm of the Gamma Function, Γ
-// (n). The syntax of the function is:
-//
-//    GAMMALN(x)
-//
-func (fn *formulaFuncs) GAMMALN(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "GAMMALN requires 1 numeric argument")
-	}
-	token := argsList.Front().Value.(formulaArg)
-	switch token.Type {
-	case ArgString:
-		arg := token.ToNumber()
-		if arg.Type == ArgNumber {
-			if arg.Number <= 0 {
-				return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
-			}
-			return newNumberFormulaArg(math.Log(math.Gamma(arg.Number)))
-		}
-	case ArgNumber:
-		if token.Number <= 0 {
-			return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
-		}
-		return newNumberFormulaArg(math.Log(math.Gamma(token.Number)))
-	}
-	return newErrorFormulaArg(formulaErrorVALUE, "GAMMALN requires 1 numeric argument")
-}
-
-// HARMEAN function calculates the harmonic mean of a supplied set of values.
-// The syntax of the function is:
-//
-//    HARMEAN(number1,[number2],...)
-//
-func (fn *formulaFuncs) HARMEAN(argsList *list.List) formulaArg {
-	if argsList.Len() < 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "HARMEAN requires at least 1 argument")
-	}
-	if min := fn.MIN(argsList); min.Number < 0 {
-		return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
-	}
-	number, val, cnt := 0.0, 0.0, 0.0
-	for token := argsList.Front(); token != nil; token = token.Next() {
-		arg := token.Value.(formulaArg)
-		switch arg.Type {
-		case ArgString:
-			num := arg.ToNumber()
-			if num.Type != ArgNumber {
-				continue
-			}
-			number = num.Number
-		case ArgNumber:
-			number = arg.Number
-		}
-		if number <= 0 {
-			return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
-		}
-		val += (1 / number)
-		cnt++
-	}
-	return newNumberFormulaArg(1 / (val / cnt))
-}
-
-// KURT function calculates the kurtosis of a supplied set of values. The
-// syntax of the function is:
-//
-//    KURT(number1,[number2],...)
-//
-func (fn *formulaFuncs) KURT(argsList *list.List) formulaArg {
-	if argsList.Len() < 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "KURT requires at least 1 argument")
-	}
-	mean, stdev := fn.AVERAGE(argsList), fn.STDEV(argsList)
-	if stdev.Number > 0 {
-		count, summer := 0.0, 0.0
-		for arg := argsList.Front(); arg != nil; arg = arg.Next() {
-			token := arg.Value.(formulaArg)
-			switch token.Type {
-			case ArgString, ArgNumber:
-				num := token.ToNumber()
-				if num.Type == ArgError {
-					continue
-				}
-				summer += math.Pow((num.Number-mean.Number)/stdev.Number, 4)
-				count++
-			case ArgList, ArgMatrix:
-				for _, row := range token.ToList() {
-					if row.Type == ArgNumber || row.Type == ArgString {
-						num := row.ToNumber()
-						if num.Type == ArgError {
-							continue
-						}
-						summer += math.Pow((num.Number-mean.Number)/stdev.Number, 4)
-						count++
-					}
-				}
-			}
-		}
-		if count > 3 {
-			return newNumberFormulaArg(summer*(count*(count+1)/((count-1)*(count-2)*(count-3))) - (3 * math.Pow(count-1, 2) / ((count - 2) * (count - 3))))
-		}
-	}
-	return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV)
-}
-
-// NORMdotDIST function calculates the Normal Probability Density Function or
-// the Cumulative Normal Distribution. Function for a supplied set of
-// parameters. The syntax of the function is:
-//
-//    NORM.DIST(x,mean,standard_dev,cumulative)
-//
-func (fn *formulaFuncs) NORMdotDIST(argsList *list.List) formulaArg {
-	if argsList.Len() != 4 {
-		return newErrorFormulaArg(formulaErrorVALUE, "NORM.DIST requires 4 arguments")
-	}
-	return fn.NORMDIST(argsList)
-}
-
-// NORMDIST function calculates the Normal Probability Density Function or the
-// Cumulative Normal Distribution. Function for a supplied set of parameters.
-// The syntax of the function is:
-//
-//    NORMDIST(x,mean,standard_dev,cumulative)
-//
-func (fn *formulaFuncs) NORMDIST(argsList *list.List) formulaArg {
-	if argsList.Len() != 4 {
-		return newErrorFormulaArg(formulaErrorVALUE, "NORMDIST requires 4 arguments")
-	}
-	var x, mean, stdDev, cumulative formulaArg
-	if x = argsList.Front().Value.(formulaArg).ToNumber(); x.Type != ArgNumber {
-		return x
-	}
-	if mean = argsList.Front().Next().Value.(formulaArg).ToNumber(); mean.Type != ArgNumber {
-		return mean
-	}
-	if stdDev = argsList.Back().Prev().Value.(formulaArg).ToNumber(); stdDev.Type != ArgNumber {
-		return stdDev
-	}
-	if cumulative = argsList.Back().Value.(formulaArg).ToBool(); cumulative.Type == ArgError {
-		return cumulative
-	}
-	if stdDev.Number < 0 {
-		return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
-	}
-	if cumulative.Number == 1 {
-		return newNumberFormulaArg(0.5 * (1 + math.Erf((x.Number-mean.Number)/(stdDev.Number*math.Sqrt(2)))))
-	}
-	return newNumberFormulaArg((1 / (math.Sqrt(2*math.Pi) * stdDev.Number)) * math.Exp(0-(math.Pow(x.Number-mean.Number, 2)/(2*(stdDev.Number*stdDev.Number)))))
-}
-
-// NORMdotINV function calculates the inverse of the Cumulative Normal
-// Distribution Function for a supplied value of x, and a supplied
-// distribution mean & standard deviation. The syntax of the function is:
-//
-//    NORM.INV(probability,mean,standard_dev)
-//
-func (fn *formulaFuncs) NORMdotINV(argsList *list.List) formulaArg {
-	if argsList.Len() != 3 {
-		return newErrorFormulaArg(formulaErrorVALUE, "NORM.INV requires 3 arguments")
-	}
-	return fn.NORMINV(argsList)
-}
-
-// NORMINV function calculates the inverse of the Cumulative Normal
-// Distribution Function for a supplied value of x, and a supplied
-// distribution mean & standard deviation. The syntax of the function is:
-//
-//    NORMINV(probability,mean,standard_dev)
-//
-func (fn *formulaFuncs) NORMINV(argsList *list.List) formulaArg {
-	if argsList.Len() != 3 {
-		return newErrorFormulaArg(formulaErrorVALUE, "NORMINV requires 3 arguments")
-	}
-	var prob, mean, stdDev formulaArg
-	if prob = argsList.Front().Value.(formulaArg).ToNumber(); prob.Type != ArgNumber {
-		return prob
-	}
-	if mean = argsList.Front().Next().Value.(formulaArg).ToNumber(); mean.Type != ArgNumber {
-		return mean
-	}
-	if stdDev = argsList.Back().Value.(formulaArg).ToNumber(); stdDev.Type != ArgNumber {
-		return stdDev
-	}
-	if prob.Number < 0 || prob.Number > 1 {
-		return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
-	}
-	if stdDev.Number < 0 {
-		return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
-	}
-	inv, err := norminv(prob.Number)
-	if err != nil {
-		return newErrorFormulaArg(err.Error(), err.Error())
-	}
-	return newNumberFormulaArg(inv*stdDev.Number + mean.Number)
-}
-
-// NORMdotSdotDIST function calculates the Standard Normal Cumulative
-// Distribution Function for a supplied value. The syntax of the function
-// is:
-//
-//    NORM.S.DIST(z)
-//
-func (fn *formulaFuncs) NORMdotSdotDIST(argsList *list.List) formulaArg {
-	if argsList.Len() != 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "NORM.S.DIST requires 2 numeric arguments")
-	}
-	args := list.New().Init()
-	args.PushBack(argsList.Front().Value.(formulaArg))
-	args.PushBack(formulaArg{Type: ArgNumber, Number: 0})
-	args.PushBack(formulaArg{Type: ArgNumber, Number: 1})
-	args.PushBack(argsList.Back().Value.(formulaArg))
-	return fn.NORMDIST(args)
-}
-
-// NORMSDIST function calculates the Standard Normal Cumulative Distribution
-// Function for a supplied value. The syntax of the function is:
-//
-//    NORMSDIST(z)
-//
-func (fn *formulaFuncs) NORMSDIST(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "NORMSDIST requires 1 numeric argument")
-	}
-	args := list.New().Init()
-	args.PushBack(argsList.Front().Value.(formulaArg))
-	args.PushBack(formulaArg{Type: ArgNumber, Number: 0})
-	args.PushBack(formulaArg{Type: ArgNumber, Number: 1})
-	args.PushBack(formulaArg{Type: ArgNumber, Number: 1, Boolean: true})
-	return fn.NORMDIST(args)
-}
-
-// NORMSINV function calculates the inverse of the Standard Normal Cumulative
-// Distribution Function for a supplied probability value. The syntax of the
-// function is:
-//
-//    NORMSINV(probability)
-//
-func (fn *formulaFuncs) NORMSINV(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "NORMSINV requires 1 numeric argument")
-	}
-	args := list.New().Init()
-	args.PushBack(argsList.Front().Value.(formulaArg))
-	args.PushBack(formulaArg{Type: ArgNumber, Number: 0})
-	args.PushBack(formulaArg{Type: ArgNumber, Number: 1})
-	return fn.NORMINV(args)
-}
-
-// NORMdotSdotINV function calculates the inverse of the Standard Normal
-// Cumulative Distribution Function for a supplied probability value. The
-// syntax of the function is:
-//
-//    NORM.S.INV(probability)
-//
-func (fn *formulaFuncs) NORMdotSdotINV(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "NORM.S.INV requires 1 numeric argument")
-	}
-	args := list.New().Init()
-	args.PushBack(argsList.Front().Value.(formulaArg))
-	args.PushBack(formulaArg{Type: ArgNumber, Number: 0})
-	args.PushBack(formulaArg{Type: ArgNumber, Number: 1})
-	return fn.NORMINV(args)
-}
-
-// norminv returns the inverse of the normal cumulative distribution for the
-// specified value.
-func norminv(p float64) (float64, error) {
-	a := map[int]float64{
-		1: -3.969683028665376e+01, 2: 2.209460984245205e+02, 3: -2.759285104469687e+02,
-		4: 1.383577518672690e+02, 5: -3.066479806614716e+01, 6: 2.506628277459239e+00,
-	}
-	b := map[int]float64{
-		1: -5.447609879822406e+01, 2: 1.615858368580409e+02, 3: -1.556989798598866e+02,
-		4: 6.680131188771972e+01, 5: -1.328068155288572e+01,
-	}
-	c := map[int]float64{
-		1: -7.784894002430293e-03, 2: -3.223964580411365e-01, 3: -2.400758277161838e+00,
-		4: -2.549732539343734e+00, 5: 4.374664141464968e+00, 6: 2.938163982698783e+00,
-	}
-	d := map[int]float64{
-		1: 7.784695709041462e-03, 2: 3.224671290700398e-01, 3: 2.445134137142996e+00,
-		4: 3.754408661907416e+00,
-	}
-	pLow := 0.02425   // Use lower region approx. below this
-	pHigh := 1 - pLow // Use upper region approx. above this
-	if 0 < p && p < pLow {
-		// Rational approximation for lower region.
-		q := math.Sqrt(-2 * math.Log(p))
-		return (((((c[1]*q+c[2])*q+c[3])*q+c[4])*q+c[5])*q + c[6]) /
-			((((d[1]*q+d[2])*q+d[3])*q+d[4])*q + 1), nil
-	} else if pLow <= p && p <= pHigh {
-		// Rational approximation for central region.
-		q := p - 0.5
-		r := q * q
-		return (((((a[1]*r+a[2])*r+a[3])*r+a[4])*r+a[5])*r + a[6]) * q /
-			(((((b[1]*r+b[2])*r+b[3])*r+b[4])*r+b[5])*r + 1), nil
-	} else if pHigh < p && p < 1 {
-		// Rational approximation for upper region.
-		q := math.Sqrt(-2 * math.Log(1-p))
-		return -(((((c[1]*q+c[2])*q+c[3])*q+c[4])*q+c[5])*q + c[6]) /
-			((((d[1]*q+d[2])*q+d[3])*q+d[4])*q + 1), nil
-	}
-	return 0, errors.New(formulaErrorNUM)
-}
-
-// kth is an implementation of the formula function LARGE and SMALL.
-func (fn *formulaFuncs) kth(name string, argsList *list.List) formulaArg {
-	if argsList.Len() != 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 2 arguments", name))
-	}
-	array := argsList.Front().Value.(formulaArg).ToList()
-	kArg := argsList.Back().Value.(formulaArg).ToNumber()
-	if kArg.Type != ArgNumber {
-		return kArg
-	}
-	k := int(kArg.Number)
-	if k < 1 {
-		return newErrorFormulaArg(formulaErrorNUM, "k should be > 0")
-	}
-	data := []float64{}
-	for _, arg := range array {
-		if numArg := arg.ToNumber(); numArg.Type == ArgNumber {
-			data = append(data, numArg.Number)
-		}
-	}
-	if len(data) < k {
-		return newErrorFormulaArg(formulaErrorNUM, "k should be <= length of array")
-	}
-	sort.Float64s(data)
-	if name == "LARGE" {
-		return newNumberFormulaArg(data[len(data)-k])
-	}
-	return newNumberFormulaArg(data[k-1])
-}
-
-// LARGE function returns the k'th largest value from an array of numeric
-// values. The syntax of the function is:
-//
-//    LARGE(array,k)
-//
-func (fn *formulaFuncs) LARGE(argsList *list.List) formulaArg {
-	return fn.kth("LARGE", argsList)
-}
-
-// MAX function returns the largest value from a supplied set of numeric
-// values. The syntax of the function is:
-//
-//    MAX(number1,[number2],...)
-//
-func (fn *formulaFuncs) MAX(argsList *list.List) formulaArg {
-	if argsList.Len() == 0 {
-		return newErrorFormulaArg(formulaErrorVALUE, "MAX requires at least 1 argument")
-	}
-	return fn.max(false, argsList)
-}
-
-// MAXA function returns the largest value from a supplied set of numeric
-// values, while counting text and the logical value FALSE as the value 0 and
-// counting the logical value TRUE as the value 1. The syntax of the function
-// is:
-//
-//    MAXA(number1,[number2],...)
-//
-func (fn *formulaFuncs) MAXA(argsList *list.List) formulaArg {
-	if argsList.Len() == 0 {
-		return newErrorFormulaArg(formulaErrorVALUE, "MAXA requires at least 1 argument")
-	}
-	return fn.max(true, argsList)
-}
-
-// max is an implementation of the formula function MAX and MAXA.
-func (fn *formulaFuncs) max(maxa bool, argsList *list.List) formulaArg {
-	max := -math.MaxFloat64
-	for token := argsList.Front(); token != nil; token = token.Next() {
-		arg := token.Value.(formulaArg)
-		switch arg.Type {
-		case ArgString:
-			if !maxa && (arg.Value() == "TRUE" || arg.Value() == "FALSE") {
-				continue
-			} else {
-				num := arg.ToBool()
-				if num.Type == ArgNumber && num.Number > max {
-					max = num.Number
-					continue
-				}
-			}
-			num := arg.ToNumber()
-			if num.Type != ArgError && num.Number > max {
-				max = num.Number
-			}
-		case ArgNumber:
-			if arg.Number > max {
-				max = arg.Number
-			}
-		case ArgList, ArgMatrix:
-			for _, row := range arg.ToList() {
-				switch row.Type {
-				case ArgString:
-					if !maxa && (row.Value() == "TRUE" || row.Value() == "FALSE") {
-						continue
-					} else {
-						num := row.ToBool()
-						if num.Type == ArgNumber && num.Number > max {
-							max = num.Number
-							continue
-						}
-					}
-					num := row.ToNumber()
-					if num.Type != ArgError && num.Number > max {
-						max = num.Number
-					}
-				case ArgNumber:
-					if row.Number > max {
-						max = row.Number
-					}
-				}
-			}
-		case ArgError:
-			return arg
-		}
-	}
-	if max == -math.MaxFloat64 {
-		max = 0
-	}
-	return newNumberFormulaArg(max)
-}
-
-// MEDIAN function returns the statistical median (the middle value) of a list
-// of supplied numbers. The syntax of the function is:
-//
-//    MEDIAN(number1,[number2],...)
-//
-func (fn *formulaFuncs) MEDIAN(argsList *list.List) formulaArg {
-	if argsList.Len() == 0 {
-		return newErrorFormulaArg(formulaErrorVALUE, "MEDIAN requires at least 1 argument")
-	}
-	var values = []float64{}
-	var median, digits float64
-	var err error
-	for token := argsList.Front(); token != nil; token = token.Next() {
-		arg := token.Value.(formulaArg)
-		switch arg.Type {
-		case ArgString:
-			num := arg.ToNumber()
-			if num.Type == ArgError {
-				return newErrorFormulaArg(formulaErrorVALUE, num.Error)
-			}
-			values = append(values, num.Number)
-		case ArgNumber:
-			values = append(values, arg.Number)
-		case ArgMatrix:
-			for _, row := range arg.Matrix {
-				for _, value := range row {
-					if value.String == "" {
-						continue
-					}
-					if digits, err = strconv.ParseFloat(value.String, 64); err != nil {
-						return newErrorFormulaArg(formulaErrorVALUE, err.Error())
-					}
-					values = append(values, digits)
-				}
-			}
-		}
-	}
-	sort.Float64s(values)
-	if len(values)%2 == 0 {
-		median = (values[len(values)/2-1] + values[len(values)/2]) / 2
-	} else {
-		median = values[len(values)/2]
-	}
-	return newNumberFormulaArg(median)
-}
-
-// MIN function returns the smallest value from a supplied set of numeric
-// values. The syntax of the function is:
-//
-//    MIN(number1,[number2],...)
-//
-func (fn *formulaFuncs) MIN(argsList *list.List) formulaArg {
-	if argsList.Len() == 0 {
-		return newErrorFormulaArg(formulaErrorVALUE, "MIN requires at least 1 argument")
-	}
-	return fn.min(false, argsList)
-}
-
-// MINA function returns the smallest value from a supplied set of numeric
-// values, while counting text and the logical value FALSE as the value 0 and
-// counting the logical value TRUE as the value 1. The syntax of the function
-// is:
-//
-//    MINA(number1,[number2],...)
-//
-func (fn *formulaFuncs) MINA(argsList *list.List) formulaArg {
-	if argsList.Len() == 0 {
-		return newErrorFormulaArg(formulaErrorVALUE, "MINA requires at least 1 argument")
-	}
-	return fn.min(true, argsList)
-}
-
-// min is an implementation of the formula function MIN and MINA.
-func (fn *formulaFuncs) min(mina bool, argsList *list.List) formulaArg {
-	min := math.MaxFloat64
-	for token := argsList.Front(); token != nil; token = token.Next() {
-		arg := token.Value.(formulaArg)
-		switch arg.Type {
-		case ArgString:
-			if !mina && (arg.Value() == "TRUE" || arg.Value() == "FALSE") {
-				continue
-			} else {
-				num := arg.ToBool()
-				if num.Type == ArgNumber && num.Number < min {
-					min = num.Number
-					continue
-				}
-			}
-			num := arg.ToNumber()
-			if num.Type != ArgError && num.Number < min {
-				min = num.Number
-			}
-		case ArgNumber:
-			if arg.Number < min {
-				min = arg.Number
-			}
-		case ArgList, ArgMatrix:
-			for _, row := range arg.ToList() {
-				switch row.Type {
-				case ArgString:
-					if !mina && (row.Value() == "TRUE" || row.Value() == "FALSE") {
-						continue
-					} else {
-						num := row.ToBool()
-						if num.Type == ArgNumber && num.Number < min {
-							min = num.Number
-							continue
-						}
-					}
-					num := row.ToNumber()
-					if num.Type != ArgError && num.Number < min {
-						min = num.Number
-					}
-				case ArgNumber:
-					if row.Number < min {
-						min = row.Number
-					}
-				}
-			}
-		case ArgError:
-			return arg
-		}
-	}
-	if min == math.MaxFloat64 {
-		min = 0
-	}
-	return newNumberFormulaArg(min)
-}
-
-// PERCENTILEdotINC function returns the k'th percentile (i.e. the value below
-// which k% of the data values fall) for a supplied range of values and a
-// supplied k. The syntax of the function is:
-//
-//    PERCENTILE.INC(array,k)
-//
-func (fn *formulaFuncs) PERCENTILEdotINC(argsList *list.List) formulaArg {
-	if argsList.Len() != 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "PERCENTILE.INC requires 2 arguments")
-	}
-	return fn.PERCENTILE(argsList)
-}
-
-// PERCENTILE function returns the k'th percentile (i.e. the value below which
-// k% of the data values fall) for a supplied range of values and a supplied
-// k. The syntax of the function is:
-//
-//    PERCENTILE(array,k)
-//
-func (fn *formulaFuncs) PERCENTILE(argsList *list.List) formulaArg {
-	if argsList.Len() != 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "PERCENTILE requires 2 arguments")
-	}
-	array := argsList.Front().Value.(formulaArg).ToList()
-	k := argsList.Back().Value.(formulaArg).ToNumber()
-	if k.Type != ArgNumber {
-		return k
-	}
-	if k.Number < 0 || k.Number > 1 {
-		return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
-	}
-	numbers := []float64{}
-	for _, arg := range array {
-		if arg.Type == ArgError {
-			return arg
-		}
-		num := arg.ToNumber()
-		if num.Type == ArgNumber {
-			numbers = append(numbers, num.Number)
-		}
-	}
-	cnt := len(numbers)
-	sort.Float64s(numbers)
-	idx := k.Number * (float64(cnt) - 1)
-	base := math.Floor(idx)
-	if idx == base {
-		return newNumberFormulaArg(numbers[int(idx)])
-	}
-	next := base + 1
-	proportion := idx - base
-	return newNumberFormulaArg(numbers[int(base)] + ((numbers[int(next)] - numbers[int(base)]) * proportion))
-}
-
-// PERMUT function calculates the number of permutations of a specified number
-// of objects from a set of objects. The syntax of the function is:
-//
-//    PERMUT(number,number_chosen)
-//
-func (fn *formulaFuncs) PERMUT(argsList *list.List) formulaArg {
-	if argsList.Len() != 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "PERMUT requires 2 numeric arguments")
-	}
-	number := argsList.Front().Value.(formulaArg).ToNumber()
-	chosen := argsList.Back().Value.(formulaArg).ToNumber()
-	if number.Type != ArgNumber {
-		return number
-	}
-	if chosen.Type != ArgNumber {
-		return chosen
-	}
-	if number.Number < chosen.Number {
-		return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
-	}
-	return newNumberFormulaArg(math.Round(fact(number.Number) / fact(number.Number-chosen.Number)))
-}
-
-// PERMUTATIONA function calculates the number of permutations, with
-// repetitions, of a specified number of objects from a set. The syntax of
-// the function is:
-//
-//    PERMUTATIONA(number,number_chosen)
-//
-func (fn *formulaFuncs) PERMUTATIONA(argsList *list.List) formulaArg {
-	if argsList.Len() < 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "PERMUTATIONA requires 2 numeric arguments")
-	}
-	number := argsList.Front().Value.(formulaArg).ToNumber()
-	chosen := argsList.Back().Value.(formulaArg).ToNumber()
-	if number.Type != ArgNumber {
-		return number
-	}
-	if chosen.Type != ArgNumber {
-		return chosen
-	}
-	num, numChosen := math.Floor(number.Number), math.Floor(chosen.Number)
-	if num < 0 || numChosen < 0 {
-		return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
-	}
-	return newNumberFormulaArg(math.Pow(num, numChosen))
-}
-
-// QUARTILE function returns a requested quartile of a supplied range of
-// values. The syntax of the function is:
-//
-//    QUARTILE(array,quart)
-//
-func (fn *formulaFuncs) QUARTILE(argsList *list.List) formulaArg {
-	if argsList.Len() != 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "QUARTILE requires 2 arguments")
-	}
-	quart := argsList.Back().Value.(formulaArg).ToNumber()
-	if quart.Type != ArgNumber {
-		return quart
-	}
-	if quart.Number < 0 || quart.Number > 4 {
-		return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
-	}
-	args := list.New().Init()
-	args.PushBack(argsList.Front().Value.(formulaArg))
-	args.PushBack(newNumberFormulaArg(quart.Number / 4))
-	return fn.PERCENTILE(args)
-}
-
-// QUARTILEdotINC function returns a requested quartile of a supplied range of
-// values. The syntax of the function is:
-//
-//    QUARTILE.INC(array,quart)
-//
-func (fn *formulaFuncs) QUARTILEdotINC(argsList *list.List) formulaArg {
-	if argsList.Len() != 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "QUARTILE.INC requires 2 arguments")
-	}
-	return fn.QUARTILE(argsList)
-}
-
-// SKEW function calculates the skewness of the distribution of a supplied set
-// of values. The syntax of the function is:
-//
-//    SKEW(number1,[number2],...)
-//
-func (fn *formulaFuncs) SKEW(argsList *list.List) formulaArg {
-	if argsList.Len() < 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "SKEW requires at least 1 argument")
-	}
-	mean, stdDev, count, summer := fn.AVERAGE(argsList), fn.STDEV(argsList), 0.0, 0.0
-	for arg := argsList.Front(); arg != nil; arg = arg.Next() {
-		token := arg.Value.(formulaArg)
-		switch token.Type {
-		case ArgNumber, ArgString:
-			num := token.ToNumber()
-			if num.Type == ArgError {
-				return num
-			}
-			summer += math.Pow((num.Number-mean.Number)/stdDev.Number, 3)
-			count++
-		case ArgList, ArgMatrix:
-			for _, row := range token.ToList() {
-				numArg := row.ToNumber()
-				if numArg.Type != ArgNumber {
-					continue
-				}
-				summer += math.Pow((numArg.Number-mean.Number)/stdDev.Number, 3)
-				count++
-			}
-		}
-	}
-	if count > 2 {
-		return newNumberFormulaArg(summer * (count / ((count - 1) * (count - 2))))
-	}
-	return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV)
-}
-
-// SMALL function returns the k'th smallest value from an array of numeric
-// values. The syntax of the function is:
-//
-//    SMALL(array,k)
-//
-func (fn *formulaFuncs) SMALL(argsList *list.List) formulaArg {
-	return fn.kth("SMALL", argsList)
-}
-
-// VARP function returns the Variance of a given set of values. The syntax of
-// the function is:
-//
-//    VARP(number1,[number2],...)
-//
-func (fn *formulaFuncs) VARP(argsList *list.List) formulaArg {
-	if argsList.Len() < 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "VARP requires at least 1 argument")
-	}
-	summerA, summerB, count := 0.0, 0.0, 0.0
-	for arg := argsList.Front(); arg != nil; arg = arg.Next() {
-		for _, token := range arg.Value.(formulaArg).ToList() {
-			if num := token.ToNumber(); num.Type == ArgNumber {
-				summerA += (num.Number * num.Number)
-				summerB += num.Number
-				count++
-			}
-		}
-	}
-	if count > 0 {
-		summerA *= count
-		summerB *= summerB
-		return newNumberFormulaArg((summerA - summerB) / (count * count))
-	}
-	return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV)
-}
-
-// VARdotP function returns the Variance of a given set of values. The syntax
-// of the function is:
-//
-//    VAR.P(number1,[number2],...)
-//
-func (fn *formulaFuncs) VARdotP(argsList *list.List) formulaArg {
-	if argsList.Len() < 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "VAR.P requires at least 1 argument")
-	}
-	return fn.VARP(argsList)
-}
-
-// Information Functions
-
-// ISBLANK function tests if a specified cell is blank (empty) and if so,
-// returns TRUE; Otherwise the function returns FALSE. The syntax of the
-// function is:
-//
-//    ISBLANK(value)
-//
-func (fn *formulaFuncs) ISBLANK(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "ISBLANK requires 1 argument")
-	}
-	token := argsList.Front().Value.(formulaArg)
-	result := "FALSE"
-	switch token.Type {
-	case ArgUnknown:
-		result = "TRUE"
-	case ArgString:
-		if token.String == "" {
-			result = "TRUE"
-		}
-	}
-	return newStringFormulaArg(result)
-}
-
-// ISERR function tests if an initial supplied expression (or value) returns
-// any Excel Error, except the #N/A error. If so, the function returns the
-// logical value TRUE; If the supplied value is not an error or is the #N/A
-// error, the ISERR function returns FALSE. The syntax of the function is:
-//
-//    ISERR(value)
-//
-func (fn *formulaFuncs) ISERR(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "ISERR requires 1 argument")
-	}
-	token := argsList.Front().Value.(formulaArg)
-	result := "FALSE"
-	if token.Type == ArgError {
-		for _, errType := range []string{
-			formulaErrorDIV, formulaErrorNAME, formulaErrorNUM,
-			formulaErrorVALUE, formulaErrorREF, formulaErrorNULL,
-			formulaErrorSPILL, formulaErrorCALC, formulaErrorGETTINGDATA,
-		} {
-			if errType == token.String {
-				result = "TRUE"
-			}
-		}
-	}
-	return newStringFormulaArg(result)
-}
-
-// ISERROR function tests if an initial supplied expression (or value) returns
-// an Excel Error, and if so, returns the logical value TRUE; Otherwise the
-// function returns FALSE. The syntax of the function is:
-//
-//    ISERROR(value)
-//
-func (fn *formulaFuncs) ISERROR(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "ISERROR requires 1 argument")
-	}
-	token := argsList.Front().Value.(formulaArg)
-	result := "FALSE"
-	if token.Type == ArgError {
-		for _, errType := range []string{
-			formulaErrorDIV, formulaErrorNAME, formulaErrorNA, formulaErrorNUM,
-			formulaErrorVALUE, formulaErrorREF, formulaErrorNULL, formulaErrorSPILL,
-			formulaErrorCALC, formulaErrorGETTINGDATA,
-		} {
-			if errType == token.String {
-				result = "TRUE"
-			}
-		}
-	}
-	return newStringFormulaArg(result)
-}
-
-// ISEVEN function tests if a supplied number (or numeric expression)
-// evaluates to an even number, and if so, returns TRUE; Otherwise, the
-// function returns FALSE. The syntax of the function is:
-//
-//    ISEVEN(value)
-//
-func (fn *formulaFuncs) ISEVEN(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "ISEVEN requires 1 argument")
-	}
-	var (
-		token   = argsList.Front().Value.(formulaArg)
-		result  = "FALSE"
-		numeric int
-		err     error
-	)
-	if token.Type == ArgString {
-		if numeric, err = strconv.Atoi(token.String); err != nil {
-			return newErrorFormulaArg(formulaErrorVALUE, err.Error())
-		}
-		if numeric == numeric/2*2 {
-			return newStringFormulaArg("TRUE")
-		}
-	}
-	return newStringFormulaArg(result)
-}
-
-// ISNA function tests if an initial supplied expression (or value) returns
-// the Excel #N/A Error, and if so, returns TRUE; Otherwise the function
-// returns FALSE. The syntax of the function is:
-//
-//    ISNA(value)
-//
-func (fn *formulaFuncs) ISNA(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "ISNA requires 1 argument")
-	}
-	token := argsList.Front().Value.(formulaArg)
-	result := "FALSE"
-	if token.Type == ArgError && token.String == formulaErrorNA {
-		result = "TRUE"
-	}
-	return newStringFormulaArg(result)
-}
-
-// ISNONTEXT function function tests if a supplied value is text. If not, the
-// function returns TRUE; If the supplied value is text, the function returns
-// FALSE. The syntax of the function is:
-//
-//    ISNONTEXT(value)
-//
-func (fn *formulaFuncs) ISNONTEXT(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "ISNONTEXT requires 1 argument")
-	}
-	token := argsList.Front().Value.(formulaArg)
-	result := "TRUE"
-	if token.Type == ArgString && token.String != "" {
-		result = "FALSE"
-	}
-	return newStringFormulaArg(result)
-}
-
-// ISNUMBER function function tests if a supplied value is a number. If so,
-// the function returns TRUE; Otherwise it returns FALSE. The syntax of the
-// function is:
-//
-//    ISNUMBER(value)
-//
-func (fn *formulaFuncs) ISNUMBER(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "ISNUMBER requires 1 argument")
-	}
-	token, result := argsList.Front().Value.(formulaArg), false
-	if token.Type == ArgString && token.String != "" {
-		if _, err := strconv.Atoi(token.String); err == nil {
-			result = true
-		}
-	}
-	return newBoolFormulaArg(result)
-}
-
-// ISODD function tests if a supplied number (or numeric expression) evaluates
-// to an odd number, and if so, returns TRUE; Otherwise, the function returns
-// FALSE. The syntax of the function is:
-//
-//    ISODD(value)
-//
-func (fn *formulaFuncs) ISODD(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "ISODD requires 1 argument")
-	}
-	var (
-		token   = argsList.Front().Value.(formulaArg)
-		result  = "FALSE"
-		numeric int
-		err     error
-	)
-	if token.Type == ArgString {
-		if numeric, err = strconv.Atoi(token.String); err != nil {
-			return newErrorFormulaArg(formulaErrorVALUE, err.Error())
-		}
-		if numeric != numeric/2*2 {
-			return newStringFormulaArg("TRUE")
-		}
-	}
-	return newStringFormulaArg(result)
-}
-
-// ISTEXT function tests if a supplied value is text, and if so, returns TRUE;
-// Otherwise, the function returns FALSE. The syntax of the function is:
-//
-//    ISTEXT(value)
-//
-func (fn *formulaFuncs) ISTEXT(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "ISTEXT requires 1 argument")
-	}
-	token := argsList.Front().Value.(formulaArg)
-	if token.ToNumber().Type != ArgError {
-		return newBoolFormulaArg(false)
-	}
-	return newBoolFormulaArg(token.Type == ArgString)
-}
-
-// N function converts data into a numeric value. The syntax of the function
-// is:
-//
-//    N(value)
-//
-func (fn *formulaFuncs) N(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "N requires 1 argument")
-	}
-	token, num := argsList.Front().Value.(formulaArg), 0.0
-	if token.Type == ArgError {
-		return token
-	}
-	if arg := token.ToNumber(); arg.Type == ArgNumber {
-		num = arg.Number
-	}
-	if token.Value() == "TRUE" {
-		num = 1
-	}
-	return newNumberFormulaArg(num)
-}
-
-// NA function returns the Excel #N/A error. This error message has the
-// meaning 'value not available' and is produced when an Excel Formula is
-// unable to find a value that it needs. The syntax of the function is:
-//
-//    NA()
-//
-func (fn *formulaFuncs) NA(argsList *list.List) formulaArg {
-	if argsList.Len() != 0 {
-		return newErrorFormulaArg(formulaErrorVALUE, "NA accepts no arguments")
-	}
-	return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
-}
-
-// SHEET function returns the Sheet number for a specified reference. The
-// syntax of the function is:
-//
-//    SHEET()
-//
-func (fn *formulaFuncs) SHEET(argsList *list.List) formulaArg {
-	if argsList.Len() != 0 {
-		return newErrorFormulaArg(formulaErrorVALUE, "SHEET accepts no arguments")
-	}
-	return newNumberFormulaArg(float64(fn.f.GetSheetIndex(fn.sheet) + 1))
-}
-
-// T function tests if a supplied value is text and if so, returns the
-// supplied text; Otherwise, the function returns an empty text string. The
-// syntax of the function is:
-//
-//    T(value)
-//
-func (fn *formulaFuncs) T(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "T requires 1 argument")
-	}
-	token := argsList.Front().Value.(formulaArg)
-	if token.Type == ArgError {
-		return token
-	}
-	if token.Type == ArgNumber {
-		return newStringFormulaArg("")
-	}
-	return newStringFormulaArg(token.Value())
-}
-
-// Logical Functions
-
-// AND function tests a number of supplied conditions and returns TRUE or
-// FALSE. The syntax of the function is:
-//
-//    AND(logical_test1,[logical_test2],...)
-//
-func (fn *formulaFuncs) AND(argsList *list.List) formulaArg {
-	if argsList.Len() == 0 {
-		return newErrorFormulaArg(formulaErrorVALUE, "AND requires at least 1 argument")
-	}
-	if argsList.Len() > 30 {
-		return newErrorFormulaArg(formulaErrorVALUE, "AND accepts at most 30 arguments")
-	}
-	var (
-		and = true
-		val float64
-		err error
-	)
-	for arg := argsList.Front(); arg != nil; arg = arg.Next() {
-		token := arg.Value.(formulaArg)
-		switch token.Type {
-		case ArgUnknown:
-			continue
-		case ArgString:
-			if token.String == "TRUE" {
-				continue
-			}
-			if token.String == "FALSE" {
-				return newStringFormulaArg(token.String)
-			}
-			if val, err = strconv.ParseFloat(token.String, 64); err != nil {
-				return newErrorFormulaArg(formulaErrorVALUE, err.Error())
-			}
-			and = and && (val != 0)
-		case ArgMatrix:
-			// TODO
-			return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
-		}
-	}
-	return newBoolFormulaArg(and)
-}
-
-// FALSE function function returns the logical value FALSE. The syntax of the
-// function is:
-//
-//    FALSE()
-//
-func (fn *formulaFuncs) FALSE(argsList *list.List) formulaArg {
-	if argsList.Len() != 0 {
-		return newErrorFormulaArg(formulaErrorVALUE, "FALSE takes no arguments")
-	}
-	return newBoolFormulaArg(false)
-}
-
-// IFERROR function receives two values (or expressions) and tests if the
-// first of these evaluates to an error. The syntax of the function is:
-//
-//    IFERROR(value,value_if_error)
-//
-func (fn *formulaFuncs) IFERROR(argsList *list.List) formulaArg {
-	if argsList.Len() != 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "IFERROR requires 2 arguments")
-	}
-	value := argsList.Front().Value.(formulaArg)
-	if value.Type != ArgError {
-		if value.Type == ArgEmpty {
-			return newNumberFormulaArg(0)
-		}
-		return value
-	}
-	return argsList.Back().Value.(formulaArg)
-}
-
-// NOT function returns the opposite to a supplied logical value. The syntax
-// of the function is:
-//
-//    NOT(logical)
-//
-func (fn *formulaFuncs) NOT(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "NOT requires 1 argument")
-	}
-	token := argsList.Front().Value.(formulaArg)
-	switch token.Type {
-	case ArgString, ArgList:
-		if strings.ToUpper(token.String) == "TRUE" {
-			return newBoolFormulaArg(false)
-		}
-		if strings.ToUpper(token.String) == "FALSE" {
-			return newBoolFormulaArg(true)
-		}
-	case ArgNumber:
-		return newBoolFormulaArg(!(token.Number != 0))
-	case ArgError:
-
-		return token
-	}
-	return newErrorFormulaArg(formulaErrorVALUE, "NOT expects 1 boolean or numeric argument")
-}
-
-// OR function tests a number of supplied conditions and returns either TRUE
-// or FALSE. The syntax of the function is:
-//
-//    OR(logical_test1,[logical_test2],...)
-//
-func (fn *formulaFuncs) OR(argsList *list.List) formulaArg {
-	if argsList.Len() == 0 {
-		return newErrorFormulaArg(formulaErrorVALUE, "OR requires at least 1 argument")
-	}
-	if argsList.Len() > 30 {
-		return newErrorFormulaArg(formulaErrorVALUE, "OR accepts at most 30 arguments")
-	}
-	var (
-		or  bool
-		val float64
-		err error
-	)
-	for arg := argsList.Front(); arg != nil; arg = arg.Next() {
-		token := arg.Value.(formulaArg)
-		switch token.Type {
-		case ArgUnknown:
-			continue
-		case ArgString:
-			if token.String == "FALSE" {
-				continue
-			}
-			if token.String == "TRUE" {
-				or = true
-				continue
-			}
-			if val, err = strconv.ParseFloat(token.String, 64); err != nil {
-				return newErrorFormulaArg(formulaErrorVALUE, err.Error())
-			}
-			or = val != 0
-		case ArgMatrix:
-			// TODO
-			return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
-		}
-	}
-	return newStringFormulaArg(strings.ToUpper(strconv.FormatBool(or)))
-}
-
-// TRUE function returns the logical value TRUE. The syntax of the function
-// is:
-//
-//    TRUE()
-//
-func (fn *formulaFuncs) TRUE(argsList *list.List) formulaArg {
-	if argsList.Len() != 0 {
-		return newErrorFormulaArg(formulaErrorVALUE, "TRUE takes no arguments")
-	}
-	return newBoolFormulaArg(true)
-}
-
-// Date and Time Functions
-
-// DATE returns a date, from a user-supplied year, month and day. The syntax
-// of the function is:
-//
-//    DATE(year,month,day)
-//
-func (fn *formulaFuncs) DATE(argsList *list.List) formulaArg {
-	if argsList.Len() != 3 {
-		return newErrorFormulaArg(formulaErrorVALUE, "DATE requires 3 number arguments")
-	}
-	year := argsList.Front().Value.(formulaArg).ToNumber()
-	month := argsList.Front().Next().Value.(formulaArg).ToNumber()
-	day := argsList.Back().Value.(formulaArg).ToNumber()
-	if year.Type != ArgNumber || month.Type != ArgNumber || day.Type != ArgNumber {
-		return newErrorFormulaArg(formulaErrorVALUE, "DATE requires 3 number arguments")
-	}
-	d := makeDate(int(year.Number), time.Month(month.Number), int(day.Number))
-	return newStringFormulaArg(timeFromExcelTime(daysBetween(excelMinTime1900.Unix(), d)+1, false).String())
-}
-
-// DATEDIF function calculates the number of days, months, or years between
-// two dates. The syntax of the function is:
-//
-//    DATEDIF(start_date,end_date,unit)
-//
-func (fn *formulaFuncs) DATEDIF(argsList *list.List) formulaArg {
-	if argsList.Len() != 3 {
-		return newErrorFormulaArg(formulaErrorVALUE, "DATEDIF requires 3 number arguments")
-	}
-	startArg, endArg := argsList.Front().Value.(formulaArg).ToNumber(), argsList.Front().Next().Value.(formulaArg).ToNumber()
-	if startArg.Type != ArgNumber || endArg.Type != ArgNumber {
-		return startArg
-	}
-	if startArg.Number > endArg.Number {
-		return newErrorFormulaArg(formulaErrorNUM, "start_date > end_date")
-	}
-	if startArg.Number == endArg.Number {
-		return newNumberFormulaArg(0)
-	}
-	unit := strings.ToLower(argsList.Back().Value.(formulaArg).Value())
-	startDate, endDate := timeFromExcelTime(startArg.Number, false), timeFromExcelTime(endArg.Number, false)
-	sy, smm, sd := startDate.Date()
-	ey, emm, ed := endDate.Date()
-	sm, em, diff := int(smm), int(emm), 0.0
-	switch unit {
-	case "d":
-		return newNumberFormulaArg(endArg.Number - startArg.Number)
-	case "y":
-		diff = float64(ey - sy)
-		if em < sm || (em == sm && ed < sd) {
-			diff--
-		}
-	case "m":
-		ydiff := ey - sy
-		mdiff := em - sm
-		if ed < sd {
-			mdiff--
-		}
-		if mdiff < 0 {
-			ydiff--
-			mdiff += 12
-		}
-		diff = float64(ydiff*12 + mdiff)
-	case "md":
-		smMD := em
-		if ed < sd {
-			smMD--
-		}
-		diff = endArg.Number - daysBetween(excelMinTime1900.Unix(), makeDate(ey, time.Month(smMD), sd)) - 1
-	case "ym":
-		diff = float64(em - sm)
-		if ed < sd {
-			diff--
-		}
-		if diff < 0 {
-			diff += 12
-		}
-	case "yd":
-		syYD := sy
-		if em < sm || (em == sm && ed < sd) {
-			syYD++
-		}
-		s := daysBetween(excelMinTime1900.Unix(), makeDate(syYD, time.Month(em), ed))
-		e := daysBetween(excelMinTime1900.Unix(), makeDate(sy, time.Month(sm), sd))
-		diff = s - e
-	default:
-		return newErrorFormulaArg(formulaErrorVALUE, "DATEDIF has invalid unit")
-	}
-	return newNumberFormulaArg(diff)
-}
-
-// NOW function returns the current date and time. The function receives no
-// arguments and therefore. The syntax of the function is:
-//
-//    NOW()
-//
-func (fn *formulaFuncs) NOW(argsList *list.List) formulaArg {
-	if argsList.Len() != 0 {
-		return newErrorFormulaArg(formulaErrorVALUE, "NOW accepts no arguments")
-	}
-	now := time.Now()
-	_, offset := now.Zone()
-	return newNumberFormulaArg(25569.0 + float64(now.Unix()+int64(offset))/86400)
-}
-
-// TODAY function returns the current date. The function has no arguments and
-// therefore. The syntax of the function is:
-//
-//    TODAY()
-//
-func (fn *formulaFuncs) TODAY(argsList *list.List) formulaArg {
-	if argsList.Len() != 0 {
-		return newErrorFormulaArg(formulaErrorVALUE, "TODAY accepts no arguments")
-	}
-	now := time.Now()
-	_, offset := now.Zone()
-	return newNumberFormulaArg(daysBetween(excelMinTime1900.Unix(), now.Unix()+int64(offset)) + 1)
-}
-
-// makeDate return date as a Unix time, the number of seconds elapsed since
-// January 1, 1970 UTC.
-func makeDate(y int, m time.Month, d int) int64 {
-	if y == 1900 && int(m) <= 2 {
-		d--
-	}
-	date := time.Date(y, m, d, 0, 0, 0, 0, time.UTC)
-	return date.Unix()
-}
-
-// daysBetween return time interval of the given start timestamp and end
-// timestamp.
-func daysBetween(startDate, endDate int64) float64 {
-	return float64(int(0.5 + float64((endDate-startDate)/86400)))
-}
-
-// Text Functions
-
-// CHAR function returns the character relating to a supplied character set
-// number (from 1 to 255). syntax of the function is:
-//
-//    CHAR(number)
-//
-func (fn *formulaFuncs) CHAR(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "CHAR requires 1 argument")
-	}
-	arg := argsList.Front().Value.(formulaArg).ToNumber()
-	if arg.Type != ArgNumber {
-		return arg
-	}
-	num := int(arg.Number)
-	if num < 0 || num > 255 {
-		return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
-	}
-	return newStringFormulaArg(fmt.Sprintf("%c", num))
-}
-
-// CLEAN removes all non-printable characters from a supplied text string. The
-// syntax of the function is:
-//
-//    CLEAN(text)
-//
-func (fn *formulaFuncs) CLEAN(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "CLEAN requires 1 argument")
-	}
-	b := bytes.Buffer{}
-	for _, c := range argsList.Front().Value.(formulaArg).String {
-		if c > 31 {
-			b.WriteRune(c)
-		}
-	}
-	return newStringFormulaArg(b.String())
-}
-
-// CODE function converts the first character of a supplied text string into
-// the associated numeric character set code used by your computer. The
-// syntax of the function is:
-//
-//    CODE(text)
-//
-func (fn *formulaFuncs) CODE(argsList *list.List) formulaArg {
-	return fn.code("CODE", argsList)
-}
-
-// code is an implementation of the formula function CODE and UNICODE.
-func (fn *formulaFuncs) code(name string, argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 1 argument", name))
-	}
-	text := argsList.Front().Value.(formulaArg).Value()
-	if len(text) == 0 {
-		if name == "CODE" {
-			return newNumberFormulaArg(0)
-		}
-		return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
-	}
-	return newNumberFormulaArg(float64(text[0]))
-}
-
-// CONCAT function joins together a series of supplied text strings into one
-// combined text string.
-//
-//    CONCAT(text1,[text2],...)
-//
-func (fn *formulaFuncs) CONCAT(argsList *list.List) formulaArg {
-	return fn.concat("CONCAT", argsList)
-}
-
-// CONCATENATE function joins together a series of supplied text strings into
-// one combined text string.
-//
-//    CONCATENATE(text1,[text2],...)
-//
-func (fn *formulaFuncs) CONCATENATE(argsList *list.List) formulaArg {
-	return fn.concat("CONCATENATE", argsList)
-}
-
-// concat is an implementation of the formula function CONCAT and CONCATENATE.
-func (fn *formulaFuncs) concat(name string, argsList *list.List) formulaArg {
-	buf := bytes.Buffer{}
-	for arg := argsList.Front(); arg != nil; arg = arg.Next() {
-		token := arg.Value.(formulaArg)
-		switch token.Type {
-		case ArgString:
-			buf.WriteString(token.String)
-		case ArgNumber:
-			if token.Boolean {
-				if token.Number == 0 {
-					buf.WriteString("FALSE")
-				} else {
-					buf.WriteString("TRUE")
-				}
-			} else {
-				buf.WriteString(token.Value())
-			}
-		default:
-			return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires arguments to be strings", name))
-		}
-	}
-	return newStringFormulaArg(buf.String())
-}
-
-// EXACT function tests if two supplied text strings or values are exactly
-// equal and if so, returns TRUE; Otherwise, the function returns FALSE. The
-// function is case-sensitive. The syntax of the function is:
-//
-//    EXACT(text1,text2)
-//
-func (fn *formulaFuncs) EXACT(argsList *list.List) formulaArg {
-	if argsList.Len() != 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "EXACT requires 2 arguments")
-	}
-	text1 := argsList.Front().Value.(formulaArg).Value()
-	text2 := argsList.Back().Value.(formulaArg).Value()
-	return newBoolFormulaArg(text1 == text2)
-}
-
-// FIXED function rounds a supplied number to a specified number of decimal
-// places and then converts this into text. The syntax of the function is:
-//
-//    FIXED(number,[decimals],[no_commas])
-//
-func (fn *formulaFuncs) FIXED(argsList *list.List) formulaArg {
-	if argsList.Len() < 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "FIXED requires at least 1 argument")
-	}
-	if argsList.Len() > 3 {
-		return newErrorFormulaArg(formulaErrorVALUE, "FIXED allows at most 3 arguments")
-	}
-	numArg := argsList.Front().Value.(formulaArg).ToNumber()
-	if numArg.Type != ArgNumber {
-		return numArg
-	}
-	precision, decimals, noCommas := 0, 0, false
-	s := strings.Split(argsList.Front().Value.(formulaArg).Value(), ".")
-	if argsList.Len() == 1 && len(s) == 2 {
-		precision = len(s[1])
-		decimals = len(s[1])
-	}
-	if argsList.Len() >= 2 {
-		decimalsArg := argsList.Front().Next().Value.(formulaArg).ToNumber()
-		if decimalsArg.Type != ArgNumber {
-			return decimalsArg
-		}
-		decimals = int(decimalsArg.Number)
-	}
-	if argsList.Len() == 3 {
-		noCommasArg := argsList.Back().Value.(formulaArg).ToBool()
-		if noCommasArg.Type == ArgError {
-			return noCommasArg
-		}
-		noCommas = noCommasArg.Boolean
-	}
-	n := math.Pow(10, float64(decimals))
-	r := numArg.Number * n
-	fixed := float64(int(r+math.Copysign(0.5, r))) / n
-	if decimals > 0 {
-		precision = decimals
-	}
-	if noCommas {
-		return newStringFormulaArg(fmt.Sprintf(fmt.Sprintf("%%.%df", precision), fixed))
-	}
-	p := message.NewPrinter(language.English)
-	return newStringFormulaArg(p.Sprintf(fmt.Sprintf("%%.%df", precision), fixed))
-}
-
-// FIND function returns the position of a specified character or sub-string
-// within a supplied text string. The function is case-sensitive. The syntax
-// of the function is:
-//
-//    FIND(find_text,within_text,[start_num])
-//
-func (fn *formulaFuncs) FIND(argsList *list.List) formulaArg {
-	return fn.find("FIND", argsList)
-}
-
-// FINDB counts each double-byte character as 2 when you have enabled the
-// editing of a language that supports DBCS and then set it as the default
-// language. Otherwise, FINDB counts each character as 1. The syntax of the
-// function is:
-//
-//    FINDB(find_text,within_text,[start_num])
-//
-func (fn *formulaFuncs) FINDB(argsList *list.List) formulaArg {
-	return fn.find("FINDB", argsList)
-}
-
-// find is an implementation of the formula function FIND and FINDB.
-func (fn *formulaFuncs) find(name string, argsList *list.List) formulaArg {
-	if argsList.Len() < 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at least 2 arguments", name))
-	}
-	if argsList.Len() > 3 {
-		return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s allows at most 3 arguments", name))
-	}
-	findText := argsList.Front().Value.(formulaArg).Value()
-	withinText := argsList.Front().Next().Value.(formulaArg).Value()
-	startNum, result := 1, 1
-	if argsList.Len() == 3 {
-		numArg := argsList.Back().Value.(formulaArg).ToNumber()
-		if numArg.Type != ArgNumber {
-			return numArg
-		}
-		if numArg.Number < 0 {
-			return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
-		}
-		startNum = int(numArg.Number)
-	}
-	if findText == "" {
-		return newNumberFormulaArg(float64(startNum))
-	}
-	for idx := range withinText {
-		if result < startNum {
-			result++
-		}
-		if strings.Index(withinText[idx:], findText) == 0 {
-			return newNumberFormulaArg(float64(result))
-		}
-		result++
-	}
-	return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
-}
-
-// LEFT function returns a specified number of characters from the start of a
-// supplied text string. The syntax of the function is:
-//
-//    LEFT(text,[num_chars])
-//
-func (fn *formulaFuncs) LEFT(argsList *list.List) formulaArg {
-	return fn.leftRight("LEFT", argsList)
-}
-
-// LEFTB returns the first character or characters in a text string, based on
-// the number of bytes you specify. The syntax of the function is:
-//
-//    LEFTB(text,[num_bytes])
-//
-func (fn *formulaFuncs) LEFTB(argsList *list.List) formulaArg {
-	return fn.leftRight("LEFTB", argsList)
-}
-
-// leftRight is an implementation of the formula function LEFT, LEFTB, RIGHT,
-// RIGHTB. TODO: support DBCS include Japanese, Chinese (Simplified), Chinese
-// (Traditional), and Korean.
-func (fn *formulaFuncs) leftRight(name string, argsList *list.List) formulaArg {
-	if argsList.Len() < 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at least 1 argument", name))
-	}
-	if argsList.Len() > 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s allows at most 2 arguments", name))
-	}
-	text, numChars := argsList.Front().Value.(formulaArg).Value(), 1
-	if argsList.Len() == 2 {
-		numArg := argsList.Back().Value.(formulaArg).ToNumber()
-		if numArg.Type != ArgNumber {
-			return numArg
-		}
-		if numArg.Number < 0 {
-			return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
-		}
-		numChars = int(numArg.Number)
-	}
-	if len(text) > numChars {
-		if name == "LEFT" || name == "LEFTB" {
-			return newStringFormulaArg(text[:numChars])
-		}
-		return newStringFormulaArg(text[len(text)-numChars:])
-	}
-	return newStringFormulaArg(text)
-}
-
-// LEN returns the length of a supplied text string. The syntax of the
-// function is:
-//
-//    LEN(text)
-//
-func (fn *formulaFuncs) LEN(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "LEN requires 1 string argument")
-	}
-	return newStringFormulaArg(strconv.Itoa(len(argsList.Front().Value.(formulaArg).String)))
-}
-
-// LENB returns the number of bytes used to represent the characters in a text
-// string. LENB counts 2 bytes per character only when a DBCS language is set
-// as the default language. Otherwise LENB behaves the same as LEN, counting
-// 1 byte per character. The syntax of the function is:
-//
-//    LENB(text)
-//
-// TODO: the languages that support DBCS include Japanese, Chinese
-// (Simplified), Chinese (Traditional), and Korean.
-func (fn *formulaFuncs) LENB(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "LENB requires 1 string argument")
-	}
-	return newStringFormulaArg(strconv.Itoa(len(argsList.Front().Value.(formulaArg).String)))
-}
-
-// LOWER converts all characters in a supplied text string to lower case. The
-// syntax of the function is:
-//
-//    LOWER(text)
-//
-func (fn *formulaFuncs) LOWER(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "LOWER requires 1 argument")
-	}
-	return newStringFormulaArg(strings.ToLower(argsList.Front().Value.(formulaArg).String))
-}
-
-// MID function returns a specified number of characters from the middle of a
-// supplied text string. The syntax of the function is:
-//
-//    MID(text,start_num,num_chars)
-//
-func (fn *formulaFuncs) MID(argsList *list.List) formulaArg {
-	return fn.mid("MID", argsList)
-}
-
-// MIDB returns a specific number of characters from a text string, starting
-// at the position you specify, based on the number of bytes you specify. The
-// syntax of the function is:
-//
-//    MID(text,start_num,num_chars)
-//
-func (fn *formulaFuncs) MIDB(argsList *list.List) formulaArg {
-	return fn.mid("MIDB", argsList)
-}
-
-// mid is an implementation of the formula function MID and MIDB. TODO:
-// support DBCS include Japanese, Chinese (Simplified), Chinese
-// (Traditional), and Korean.
-func (fn *formulaFuncs) mid(name string, argsList *list.List) formulaArg {
-	if argsList.Len() != 3 {
-		return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 3 arguments", name))
-	}
-	text := argsList.Front().Value.(formulaArg).Value()
-	startNumArg, numCharsArg := argsList.Front().Next().Value.(formulaArg).ToNumber(), argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
-	if startNumArg.Type != ArgNumber {
-		return startNumArg
-	}
-	if numCharsArg.Type != ArgNumber {
-		return numCharsArg
-	}
-	startNum := int(startNumArg.Number)
-	if startNum < 0 {
-		return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
-	}
-	textLen := len(text)
-	if startNum > textLen {
-		return newStringFormulaArg("")
-	}
-	startNum--
-	endNum := startNum + int(numCharsArg.Number)
-	if endNum > textLen+1 {
-		return newStringFormulaArg(text[startNum:])
-	}
-	return newStringFormulaArg(text[startNum:endNum])
-}
-
-// PROPER converts all characters in a supplied text string to proper case
-// (i.e. all letters that do not immediately follow another letter are set to
-// upper case and all other characters are lower case). The syntax of the
-// function is:
-//
-//    PROPER(text)
-//
-func (fn *formulaFuncs) PROPER(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "PROPER requires 1 argument")
-	}
-	buf := bytes.Buffer{}
-	isLetter := false
-	for _, char := range argsList.Front().Value.(formulaArg).String {
-		if !isLetter && unicode.IsLetter(char) {
-			buf.WriteRune(unicode.ToUpper(char))
-		} else {
-			buf.WriteRune(unicode.ToLower(char))
-		}
-		isLetter = unicode.IsLetter(char)
-	}
-	return newStringFormulaArg(buf.String())
-}
-
-// REPLACE function replaces all or part of a text string with another string.
-// The syntax of the function is:
-//
-//    REPLACE(old_text,start_num,num_chars,new_text)
-//
-func (fn *formulaFuncs) REPLACE(argsList *list.List) formulaArg {
-	return fn.replace("REPLACE", argsList)
-}
-
-// REPLACEB replaces part of a text string, based on the number of bytes you
-// specify, with a different text string.
-//
-//    REPLACEB(old_text,start_num,num_chars,new_text)
-//
-func (fn *formulaFuncs) REPLACEB(argsList *list.List) formulaArg {
-	return fn.replace("REPLACEB", argsList)
-}
-
-// replace is an implementation of the formula function REPLACE and REPLACEB.
-// TODO: support DBCS include Japanese, Chinese (Simplified), Chinese
-// (Traditional), and Korean.
-func (fn *formulaFuncs) replace(name string, argsList *list.List) formulaArg {
-	if argsList.Len() != 4 {
-		return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 4 arguments", name))
-	}
-	oldText, newText := argsList.Front().Value.(formulaArg).Value(), argsList.Back().Value.(formulaArg).Value()
-	startNumArg, numCharsArg := argsList.Front().Next().Value.(formulaArg).ToNumber(), argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
-	if startNumArg.Type != ArgNumber {
-		return startNumArg
-	}
-	if numCharsArg.Type != ArgNumber {
-		return numCharsArg
-	}
-	oldTextLen, startIdx := len(oldText), int(startNumArg.Number)
-	if startIdx > oldTextLen {
-		startIdx = oldTextLen + 1
-	}
-	endIdx := startIdx + int(numCharsArg.Number)
-	if endIdx > oldTextLen {
-		endIdx = oldTextLen + 1
-	}
-	if startIdx < 1 || endIdx < 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
-	}
-	result := oldText[:startIdx-1] + newText + oldText[endIdx-1:]
-	return newStringFormulaArg(result)
-}
-
-// REPT function returns a supplied text string, repeated a specified number
-// of times. The syntax of the function is:
-//
-//    REPT(text,number_times)
-//
-func (fn *formulaFuncs) REPT(argsList *list.List) formulaArg {
-	if argsList.Len() != 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "REPT requires 2 arguments")
-	}
-	text := argsList.Front().Value.(formulaArg)
-	if text.Type != ArgString {
-		return newErrorFormulaArg(formulaErrorVALUE, "REPT requires first argument to be a string")
-	}
-	times := argsList.Back().Value.(formulaArg).ToNumber()
-	if times.Type != ArgNumber {
-		return newErrorFormulaArg(formulaErrorVALUE, "REPT requires second argument to be a number")
-	}
-	if times.Number < 0 {
-		return newErrorFormulaArg(formulaErrorVALUE, "REPT requires second argument to be >= 0")
-	}
-	if times.Number == 0 {
-		return newStringFormulaArg("")
-	}
-	buf := bytes.Buffer{}
-	for i := 0; i < int(times.Number); i++ {
-		buf.WriteString(text.String)
-	}
-	return newStringFormulaArg(buf.String())
-}
-
-// RIGHT function returns a specified number of characters from the end of a
-// supplied text string. The syntax of the function is:
-//
-//    RIGHT(text,[num_chars])
-//
-func (fn *formulaFuncs) RIGHT(argsList *list.List) formulaArg {
-	return fn.leftRight("RIGHT", argsList)
-}
-
-// RIGHTB returns the last character or characters in a text string, based on
-// the number of bytes you specify. The syntax of the function is:
-//
-//    RIGHTB(text,[num_bytes])
-//
-func (fn *formulaFuncs) RIGHTB(argsList *list.List) formulaArg {
-	return fn.leftRight("RIGHTB", argsList)
-}
-
-// SUBSTITUTE function replaces one or more instances of a given text string,
-// within an original text string. The syntax of the function is:
-//
-//    SUBSTITUTE(text,old_text,new_text,[instance_num])
-//
-func (fn *formulaFuncs) SUBSTITUTE(argsList *list.List) formulaArg {
-	if argsList.Len() != 3 && argsList.Len() != 4 {
-		return newErrorFormulaArg(formulaErrorVALUE, "SUBSTITUTE requires 3 or 4 arguments")
-	}
-	text, oldText := argsList.Front().Value.(formulaArg), argsList.Front().Next().Value.(formulaArg)
-	newText, instanceNum := argsList.Front().Next().Next().Value.(formulaArg), 0
-	if argsList.Len() == 3 {
-		return newStringFormulaArg(strings.Replace(text.Value(), oldText.Value(), newText.Value(), -1))
-	}
-	instanceNumArg := argsList.Back().Value.(formulaArg).ToNumber()
-	if instanceNumArg.Type != ArgNumber {
-		return instanceNumArg
-	}
-	instanceNum = int(instanceNumArg.Number)
-	if instanceNum < 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "instance_num should be > 0")
-	}
-	str, oldTextLen, count, chars, pos := text.Value(), len(oldText.Value()), instanceNum, 0, -1
-	for {
-		count--
-		index := strings.Index(str, oldText.Value())
-		if index == -1 {
-			pos = -1
-			break
-		} else {
-			pos = index + chars
-			if count == 0 {
-				break
-			}
-			idx := oldTextLen + index
-			chars += idx
-			str = str[idx:]
-		}
-	}
-	if pos == -1 {
-		return newStringFormulaArg(text.Value())
-	}
-	pre, post := text.Value()[:pos], text.Value()[pos+oldTextLen:]
-	return newStringFormulaArg(pre + newText.Value() + post)
-}
-
-// TRIM removes extra spaces (i.e. all spaces except for single spaces between
-// words or characters) from a supplied text string. The syntax of the
-// function is:
-//
-//    TRIM(text)
-//
-func (fn *formulaFuncs) TRIM(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "TRIM requires 1 argument")
-	}
-	return newStringFormulaArg(strings.TrimSpace(argsList.Front().Value.(formulaArg).String))
-}
-
-// UNICHAR returns the Unicode character that is referenced by the given
-// numeric value. The syntax of the function is:
-//
-//    UNICHAR(number)
-//
-func (fn *formulaFuncs) UNICHAR(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "UNICHAR requires 1 argument")
-	}
-	numArg := argsList.Front().Value.(formulaArg).ToNumber()
-	if numArg.Type != ArgNumber {
-		return numArg
-	}
-	if numArg.Number <= 0 || numArg.Number > 55295 {
-		return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
-	}
-	return newStringFormulaArg(string(rune(numArg.Number)))
-}
-
-// UNICODE function returns the code point for the first character of a
-// supplied text string. The syntax of the function is:
-//
-//    UNICODE(text)
-//
-func (fn *formulaFuncs) UNICODE(argsList *list.List) formulaArg {
-	return fn.code("UNICODE", argsList)
-}
-
-// UPPER converts all characters in a supplied text string to upper case. The
-// syntax of the function is:
-//
-//    UPPER(text)
-//
-func (fn *formulaFuncs) UPPER(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "UPPER requires 1 argument")
-	}
-	return newStringFormulaArg(strings.ToUpper(argsList.Front().Value.(formulaArg).String))
-}
-
-// Conditional Functions
-
-// IF function tests a supplied condition and returns one result if the
-// condition evaluates to TRUE, and another result if the condition evaluates
-// to FALSE. The syntax of the function is:
-//
-//    IF(logical_test,value_if_true,value_if_false)
-//
-func (fn *formulaFuncs) IF(argsList *list.List) formulaArg {
-	if argsList.Len() == 0 {
-		return newErrorFormulaArg(formulaErrorVALUE, "IF requires at least 1 argument")
-	}
-	if argsList.Len() > 3 {
-		return newErrorFormulaArg(formulaErrorVALUE, "IF accepts at most 3 arguments")
-	}
-	token := argsList.Front().Value.(formulaArg)
-	var (
-		cond   bool
-		err    error
-		result string
-	)
-	switch token.Type {
-	case ArgString:
-		if cond, err = strconv.ParseBool(token.String); err != nil {
-			return newErrorFormulaArg(formulaErrorVALUE, err.Error())
-		}
-		if argsList.Len() == 1 {
-			return newBoolFormulaArg(cond)
-		}
-		if cond {
-			return newStringFormulaArg(argsList.Front().Next().Value.(formulaArg).String)
-		}
-		if argsList.Len() == 3 {
-			result = argsList.Back().Value.(formulaArg).String
-		}
-	}
-	return newStringFormulaArg(result)
-}
-
-// Lookup and Reference Functions
-
-// CHOOSE function returns a value from an array, that corresponds to a
-// supplied index number (position). The syntax of the function is:
-//
-//    CHOOSE(index_num,value1,[value2],...)
-//
-func (fn *formulaFuncs) CHOOSE(argsList *list.List) formulaArg {
-	if argsList.Len() < 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "CHOOSE requires 2 arguments")
-	}
-	idx, err := strconv.Atoi(argsList.Front().Value.(formulaArg).String)
-	if err != nil {
-		return newErrorFormulaArg(formulaErrorVALUE, "CHOOSE requires first argument of type number")
-	}
-	if argsList.Len() <= idx {
-		return newErrorFormulaArg(formulaErrorVALUE, "index_num should be <= to the number of values")
-	}
-	arg := argsList.Front()
-	for i := 0; i < idx; i++ {
-		arg = arg.Next()
-	}
-	var result formulaArg
-	switch arg.Value.(formulaArg).Type {
-	case ArgString:
-		result = newStringFormulaArg(arg.Value.(formulaArg).String)
-	case ArgMatrix:
-		result = newMatrixFormulaArg(arg.Value.(formulaArg).Matrix)
-	}
-	return result
-}
-
-// deepMatchRune finds whether the text deep matches/satisfies the pattern
-// string.
-func deepMatchRune(str, pattern []rune, simple bool) bool {
-	for len(pattern) > 0 {
-		switch pattern[0] {
-		default:
-			if len(str) == 0 || str[0] != pattern[0] {
-				return false
-			}
-		case '?':
-			if len(str) == 0 && !simple {
-				return false
-			}
-		case '*':
-			return deepMatchRune(str, pattern[1:], simple) ||
-				(len(str) > 0 && deepMatchRune(str[1:], pattern, simple))
-		}
-		str = str[1:]
-		pattern = pattern[1:]
-	}
-	return len(str) == 0 && len(pattern) == 0
-}
-
-// matchPattern finds whether the text matches or satisfies the pattern
-// string. The pattern supports '*' and '?' wildcards in the pattern string.
-func matchPattern(pattern, name string) (matched bool) {
-	if pattern == "" {
-		return name == pattern
-	}
-	if pattern == "*" {
-		return true
-	}
-	rname, rpattern := make([]rune, 0, len(name)), make([]rune, 0, len(pattern))
-	for _, r := range name {
-		rname = append(rname, r)
-	}
-	for _, r := range pattern {
-		rpattern = append(rpattern, r)
-	}
-	simple := false // Does extended wildcard '*' and '?' match.
-	return deepMatchRune(rname, rpattern, simple)
-}
-
-// compareFormulaArg compares the left-hand sides and the right-hand sides
-// formula arguments by given conditions such as case sensitive, if exact
-// match, and make compare result as formula criteria condition type.
-func compareFormulaArg(lhs, rhs formulaArg, caseSensitive, exactMatch bool) byte {
-	if lhs.Type != rhs.Type {
-		return criteriaErr
-	}
-	switch lhs.Type {
-	case ArgNumber:
-		if lhs.Number == rhs.Number {
-			return criteriaEq
-		}
-		if lhs.Number < rhs.Number {
-			return criteriaL
-		}
-		return criteriaG
-	case ArgString:
-		ls, rs := lhs.String, rhs.String
-		if !caseSensitive {
-			ls, rs = strings.ToLower(ls), strings.ToLower(rs)
-		}
-		if exactMatch {
-			match := matchPattern(rs, ls)
-			if match {
-				return criteriaEq
-			}
-			return criteriaG
-		}
-		switch strings.Compare(ls, rs) {
-		case 1:
-			return criteriaG
-		case -1:
-			return criteriaL
-		case 0:
-			return criteriaEq
-		}
-		return criteriaErr
-	case ArgEmpty:
-		return criteriaEq
-	case ArgList:
-		return compareFormulaArgList(lhs, rhs, caseSensitive, exactMatch)
-	case ArgMatrix:
-		return compareFormulaArgMatrix(lhs, rhs, caseSensitive, exactMatch)
-	}
-	return criteriaErr
-}
-
-// compareFormulaArgList compares the left-hand sides and the right-hand sides
-// list type formula arguments.
-func compareFormulaArgList(lhs, rhs formulaArg, caseSensitive, exactMatch bool) byte {
-	if len(lhs.List) < len(rhs.List) {
-		return criteriaL
-	}
-	if len(lhs.List) > len(rhs.List) {
-		return criteriaG
-	}
-	for arg := range lhs.List {
-		criteria := compareFormulaArg(lhs.List[arg], rhs.List[arg], caseSensitive, exactMatch)
-		if criteria != criteriaEq {
-			return criteria
-		}
-	}
-	return criteriaEq
-}
-
-// compareFormulaArgMatrix compares the left-hand sides and the right-hand sides
-// matrix type formula arguments.
-func compareFormulaArgMatrix(lhs, rhs formulaArg, caseSensitive, exactMatch bool) byte {
-	if len(lhs.Matrix) < len(rhs.Matrix) {
-		return criteriaL
-	}
-	if len(lhs.Matrix) > len(rhs.Matrix) {
-		return criteriaG
-	}
-	for i := range lhs.Matrix {
-		left := lhs.Matrix[i]
-		right := lhs.Matrix[i]
-		if len(left) < len(right) {
-			return criteriaL
-		}
-		if len(left) > len(right) {
-			return criteriaG
-		}
-		for arg := range left {
-			criteria := compareFormulaArg(left[arg], right[arg], caseSensitive, exactMatch)
-			if criteria != criteriaEq {
-				return criteria
-			}
-		}
-	}
-	return criteriaEq
-}
-
-// COLUMN function returns the first column number within a supplied reference
-// or the number of the current column. The syntax of the function is:
-//
-//    COLUMN([reference])
-//
-func (fn *formulaFuncs) COLUMN(argsList *list.List) formulaArg {
-	if argsList.Len() > 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "COLUMN requires at most 1 argument")
-	}
-	if argsList.Len() == 1 {
-		if argsList.Front().Value.(formulaArg).cellRanges != nil && argsList.Front().Value.(formulaArg).cellRanges.Len() > 0 {
-			return newNumberFormulaArg(float64(argsList.Front().Value.(formulaArg).cellRanges.Front().Value.(cellRange).From.Col))
-		}
-		if argsList.Front().Value.(formulaArg).cellRefs != nil && argsList.Front().Value.(formulaArg).cellRefs.Len() > 0 {
-			return newNumberFormulaArg(float64(argsList.Front().Value.(formulaArg).cellRefs.Front().Value.(cellRef).Col))
-		}
-		return newErrorFormulaArg(formulaErrorVALUE, "invalid reference")
-	}
-	col, _, _ := CellNameToCoordinates(fn.cell)
-	return newNumberFormulaArg(float64(col))
-}
-
-// COLUMNS function receives an Excel range and returns the number of columns
-// that are contained within the range. The syntax of the function is:
-//
-//    COLUMNS(array)
-//
-func (fn *formulaFuncs) COLUMNS(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "COLUMNS requires 1 argument")
-	}
-	var min, max int
-	if argsList.Front().Value.(formulaArg).cellRanges != nil && argsList.Front().Value.(formulaArg).cellRanges.Len() > 0 {
-		crs := argsList.Front().Value.(formulaArg).cellRanges
-		for cr := crs.Front(); cr != nil; cr = cr.Next() {
-			if min == 0 {
-				min = cr.Value.(cellRange).From.Col
-			}
-			if min > cr.Value.(cellRange).From.Col {
-				min = cr.Value.(cellRange).From.Col
-			}
-			if min > cr.Value.(cellRange).To.Col {
-				min = cr.Value.(cellRange).To.Col
-			}
-			if max < cr.Value.(cellRange).To.Col {
-				max = cr.Value.(cellRange).To.Col
-			}
-			if max < cr.Value.(cellRange).From.Col {
-				max = cr.Value.(cellRange).From.Col
-			}
-		}
-	}
-	if argsList.Front().Value.(formulaArg).cellRefs != nil && argsList.Front().Value.(formulaArg).cellRefs.Len() > 0 {
-		cr := argsList.Front().Value.(formulaArg).cellRefs
-		for refs := cr.Front(); refs != nil; refs = refs.Next() {
-			if min == 0 {
-				min = refs.Value.(cellRef).Col
-			}
-			if min > refs.Value.(cellRef).Col {
-				min = refs.Value.(cellRef).Col
-			}
-			if max < refs.Value.(cellRef).Col {
-				max = refs.Value.(cellRef).Col
-			}
-		}
-	}
-	if max == TotalColumns {
-		return newNumberFormulaArg(float64(TotalColumns))
-	}
-	result := max - min + 1
-	if max == min {
-		if min == 0 {
-			return newErrorFormulaArg(formulaErrorVALUE, "invalid reference")
-		}
-		return newNumberFormulaArg(float64(1))
-	}
-	return newNumberFormulaArg(float64(result))
-}
-
-// HLOOKUP function 'looks up' a given value in the top row of a data array
-// (or table), and returns the corresponding value from another row of the
-// array. The syntax of the function is:
-//
-//    HLOOKUP(lookup_value,table_array,row_index_num,[range_lookup])
-//
-func (fn *formulaFuncs) HLOOKUP(argsList *list.List) formulaArg {
-	if argsList.Len() < 3 {
-		return newErrorFormulaArg(formulaErrorVALUE, "HLOOKUP requires at least 3 arguments")
-	}
-	if argsList.Len() > 4 {
-		return newErrorFormulaArg(formulaErrorVALUE, "HLOOKUP requires at most 4 arguments")
-	}
-	lookupValue := argsList.Front().Value.(formulaArg)
-	tableArray := argsList.Front().Next().Value.(formulaArg)
-	if tableArray.Type != ArgMatrix {
-		return newErrorFormulaArg(formulaErrorVALUE, "HLOOKUP requires second argument of table array")
-	}
-	rowArg := argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
-	if rowArg.Type != ArgNumber {
-		return newErrorFormulaArg(formulaErrorVALUE, "HLOOKUP requires numeric row argument")
-	}
-	rowIdx, matchIdx, wasExact, exactMatch := int(rowArg.Number)-1, -1, false, false
-	if argsList.Len() == 4 {
-		rangeLookup := argsList.Back().Value.(formulaArg).ToBool()
-		if rangeLookup.Type == ArgError {
-			return newErrorFormulaArg(formulaErrorVALUE, rangeLookup.Error)
-		}
-		if rangeLookup.Number == 0 {
-			exactMatch = true
-		}
-	}
-	row := tableArray.Matrix[0]
-	if exactMatch || len(tableArray.Matrix) == TotalRows {
-	start:
-		for idx, mtx := range row {
-			lhs := mtx
-			switch lookupValue.Type {
-			case ArgNumber:
-				if !lookupValue.Boolean {
-					lhs = mtx.ToNumber()
-					if lhs.Type == ArgError {
-						lhs = mtx
-					}
-				}
-			case ArgMatrix:
-				lhs = tableArray
-			}
-			if compareFormulaArg(lhs, lookupValue, false, exactMatch) == criteriaEq {
-				matchIdx = idx
-				wasExact = true
-				break start
-			}
-		}
-	} else {
-		matchIdx, wasExact = hlookupBinarySearch(row, lookupValue)
-	}
-	if matchIdx == -1 {
-		return newErrorFormulaArg(formulaErrorNA, "HLOOKUP no result found")
-	}
-	if rowIdx < 0 || rowIdx >= len(tableArray.Matrix) {
-		return newErrorFormulaArg(formulaErrorNA, "HLOOKUP has invalid row index")
-	}
-	row = tableArray.Matrix[rowIdx]
-	if wasExact || !exactMatch {
-		return row[matchIdx]
-	}
-	return newErrorFormulaArg(formulaErrorNA, "HLOOKUP no result found")
-}
-
-// VLOOKUP function 'looks up' a given value in the left-hand column of a
-// data array (or table), and returns the corresponding value from another
-// column of the array. The syntax of the function is:
-//
-//    VLOOKUP(lookup_value,table_array,col_index_num,[range_lookup])
-//
-func (fn *formulaFuncs) VLOOKUP(argsList *list.List) formulaArg {
-	if argsList.Len() < 3 {
-		return newErrorFormulaArg(formulaErrorVALUE, "VLOOKUP requires at least 3 arguments")
-	}
-	if argsList.Len() > 4 {
-		return newErrorFormulaArg(formulaErrorVALUE, "VLOOKUP requires at most 4 arguments")
-	}
-	lookupValue := argsList.Front().Value.(formulaArg)
-	tableArray := argsList.Front().Next().Value.(formulaArg)
-	if tableArray.Type != ArgMatrix {
-		return newErrorFormulaArg(formulaErrorVALUE, "VLOOKUP requires second argument of table array")
-	}
-	colIdx := argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
-	if colIdx.Type != ArgNumber {
-		return newErrorFormulaArg(formulaErrorVALUE, "VLOOKUP requires numeric col argument")
-	}
-	col, matchIdx, wasExact, exactMatch := int(colIdx.Number)-1, -1, false, false
-	if argsList.Len() == 4 {
-		rangeLookup := argsList.Back().Value.(formulaArg).ToBool()
-		if rangeLookup.Type == ArgError {
-			return newErrorFormulaArg(formulaErrorVALUE, rangeLookup.Error)
-		}
-		if rangeLookup.Number == 0 {
-			exactMatch = true
-		}
-	}
-	if exactMatch || len(tableArray.Matrix) == TotalRows {
-	start:
-		for idx, mtx := range tableArray.Matrix {
-			lhs := mtx[0]
-			switch lookupValue.Type {
-			case ArgNumber:
-				if !lookupValue.Boolean {
-					lhs = mtx[0].ToNumber()
-					if lhs.Type == ArgError {
-						lhs = mtx[0]
-					}
-				}
-			case ArgMatrix:
-				lhs = tableArray
-			}
-			if compareFormulaArg(lhs, lookupValue, false, exactMatch) == criteriaEq {
-				matchIdx = idx
-				wasExact = true
-				break start
-			}
-		}
-	} else {
-		matchIdx, wasExact = vlookupBinarySearch(tableArray, lookupValue)
-	}
-	if matchIdx == -1 {
-		return newErrorFormulaArg(formulaErrorNA, "VLOOKUP no result found")
-	}
-	mtx := tableArray.Matrix[matchIdx]
-	if col < 0 || col >= len(mtx) {
-		return newErrorFormulaArg(formulaErrorNA, "VLOOKUP has invalid column index")
-	}
-	if wasExact || !exactMatch {
-		return mtx[col]
-	}
-	return newErrorFormulaArg(formulaErrorNA, "VLOOKUP no result found")
-}
-
-// vlookupBinarySearch finds the position of a target value when range lookup
-// is TRUE, if the data of table array can't guarantee be sorted, it will
-// return wrong result.
-func vlookupBinarySearch(tableArray, lookupValue formulaArg) (matchIdx int, wasExact bool) {
-	var low, high, lastMatchIdx int = 0, len(tableArray.Matrix) - 1, -1
-	for low <= high {
-		var mid int = low + (high-low)/2
-		mtx := tableArray.Matrix[mid]
-		lhs := mtx[0]
-		switch lookupValue.Type {
-		case ArgNumber:
-			if !lookupValue.Boolean {
-				lhs = mtx[0].ToNumber()
-				if lhs.Type == ArgError {
-					lhs = mtx[0]
-				}
-			}
-		case ArgMatrix:
-			lhs = tableArray
-		}
-		result := compareFormulaArg(lhs, lookupValue, false, false)
-		if result == criteriaEq {
-			matchIdx, wasExact = mid, true
-			return
-		} else if result == criteriaG {
-			high = mid - 1
-		} else if result == criteriaL {
-			matchIdx, low = mid, mid+1
-			if lhs.Value() != "" {
-				lastMatchIdx = matchIdx
-			}
-		} else {
-			return -1, false
-		}
-	}
-	matchIdx, wasExact = lastMatchIdx, true
-	return
-}
-
-// vlookupBinarySearch finds the position of a target value when range lookup
-// is TRUE, if the data of table array can't guarantee be sorted, it will
-// return wrong result.
-func hlookupBinarySearch(row []formulaArg, lookupValue formulaArg) (matchIdx int, wasExact bool) {
-	var low, high, lastMatchIdx int = 0, len(row) - 1, -1
-	for low <= high {
-		var mid int = low + (high-low)/2
-		mtx := row[mid]
-		result := compareFormulaArg(mtx, lookupValue, false, false)
-		if result == criteriaEq {
-			matchIdx, wasExact = mid, true
-			return
-		} else if result == criteriaG {
-			high = mid - 1
-		} else if result == criteriaL {
-			low, lastMatchIdx = mid+1, mid
-		} else {
-			return -1, false
-		}
-	}
-	matchIdx, wasExact = lastMatchIdx, true
-	return
-}
-
-// LOOKUP function performs an approximate match lookup in a one-column or
-// one-row range, and returns the corresponding value from another one-column
-// or one-row range. The syntax of the function is:
-//
-//    LOOKUP(lookup_value,lookup_vector,[result_vector])
-//
-func (fn *formulaFuncs) LOOKUP(argsList *list.List) formulaArg {
-	if argsList.Len() < 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "LOOKUP requires at least 2 arguments")
-	}
-	if argsList.Len() > 3 {
-		return newErrorFormulaArg(formulaErrorVALUE, "LOOKUP requires at most 3 arguments")
-	}
-	lookupValue := argsList.Front().Value.(formulaArg)
-	lookupVector := argsList.Front().Next().Value.(formulaArg)
-	if lookupVector.Type != ArgMatrix && lookupVector.Type != ArgList {
-		return newErrorFormulaArg(formulaErrorVALUE, "LOOKUP requires second argument of table array")
-	}
-	cols, matchIdx := lookupCol(lookupVector), -1
-	for idx, col := range cols {
-		lhs := lookupValue
-		switch col.Type {
-		case ArgNumber:
-			lhs = lhs.ToNumber()
-			if !col.Boolean {
-				if lhs.Type == ArgError {
-					lhs = lookupValue
-				}
-			}
-		}
-		if compareFormulaArg(lhs, col, false, false) == criteriaEq {
-			matchIdx = idx
-			break
-		}
-	}
-	column := cols
-	if argsList.Len() == 3 {
-		column = lookupCol(argsList.Back().Value.(formulaArg))
-	}
-	if matchIdx < 0 || matchIdx >= len(column) {
-		return newErrorFormulaArg(formulaErrorNA, "LOOKUP no result found")
-	}
-	return column[matchIdx]
-}
-
-// lookupCol extract columns for LOOKUP.
-func lookupCol(arr formulaArg) []formulaArg {
-	col := arr.List
-	if arr.Type == ArgMatrix {
-		col = nil
-		for _, r := range arr.Matrix {
-			if len(r) > 0 {
-				col = append(col, r[0])
-				continue
-			}
-			col = append(col, newEmptyFormulaArg())
-		}
-	}
-	return col
-}
-
-// ROW function returns the first row number within a supplied reference or
-// the number of the current row. The syntax of the function is:
-//
-//    ROW([reference])
-//
-func (fn *formulaFuncs) ROW(argsList *list.List) formulaArg {
-	if argsList.Len() > 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "ROW requires at most 1 argument")
-	}
-	if argsList.Len() == 1 {
-		if argsList.Front().Value.(formulaArg).cellRanges != nil && argsList.Front().Value.(formulaArg).cellRanges.Len() > 0 {
-			return newNumberFormulaArg(float64(argsList.Front().Value.(formulaArg).cellRanges.Front().Value.(cellRange).From.Row))
-		}
-		if argsList.Front().Value.(formulaArg).cellRefs != nil && argsList.Front().Value.(formulaArg).cellRefs.Len() > 0 {
-			return newNumberFormulaArg(float64(argsList.Front().Value.(formulaArg).cellRefs.Front().Value.(cellRef).Row))
-		}
-		return newErrorFormulaArg(formulaErrorVALUE, "invalid reference")
-	}
-	_, row, _ := CellNameToCoordinates(fn.cell)
-	return newNumberFormulaArg(float64(row))
-}
-
-// ROWS function takes an Excel range and returns the number of rows that are
-// contained within the range. The syntax of the function is:
-//
-//    ROWS(array)
-//
-func (fn *formulaFuncs) ROWS(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "ROWS requires 1 argument")
-	}
-	var min, max int
-	if argsList.Front().Value.(formulaArg).cellRanges != nil && argsList.Front().Value.(formulaArg).cellRanges.Len() > 0 {
-		crs := argsList.Front().Value.(formulaArg).cellRanges
-		for cr := crs.Front(); cr != nil; cr = cr.Next() {
-			if min == 0 {
-				min = cr.Value.(cellRange).From.Row
-			}
-			if min > cr.Value.(cellRange).From.Row {
-				min = cr.Value.(cellRange).From.Row
-			}
-			if min > cr.Value.(cellRange).To.Row {
-				min = cr.Value.(cellRange).To.Row
-			}
-			if max < cr.Value.(cellRange).To.Row {
-				max = cr.Value.(cellRange).To.Row
-			}
-			if max < cr.Value.(cellRange).From.Row {
-				max = cr.Value.(cellRange).From.Row
-			}
-		}
-	}
-	if argsList.Front().Value.(formulaArg).cellRefs != nil && argsList.Front().Value.(formulaArg).cellRefs.Len() > 0 {
-		cr := argsList.Front().Value.(formulaArg).cellRefs
-		for refs := cr.Front(); refs != nil; refs = refs.Next() {
-			if min == 0 {
-				min = refs.Value.(cellRef).Row
-			}
-			if min > refs.Value.(cellRef).Row {
-				min = refs.Value.(cellRef).Row
-			}
-			if max < refs.Value.(cellRef).Row {
-				max = refs.Value.(cellRef).Row
-			}
-		}
-	}
-	if max == TotalRows {
-		return newStringFormulaArg(strconv.Itoa(TotalRows))
-	}
-	result := max - min + 1
-	if max == min {
-		if min == 0 {
-			return newErrorFormulaArg(formulaErrorVALUE, "invalid reference")
-		}
-		return newNumberFormulaArg(float64(1))
-	}
-	return newStringFormulaArg(strconv.Itoa(result))
-}
-
-// Web Functions
-
-// ENCODEURL function returns a URL-encoded string, replacing certain
-// non-alphanumeric characters with the percentage symbol (%) and a
-// hexadecimal number. The syntax of the function is:
-//
-//    ENCODEURL(url)
-//
-func (fn *formulaFuncs) ENCODEURL(argsList *list.List) formulaArg {
-	if argsList.Len() != 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "ENCODEURL requires 1 argument")
-	}
-	token := argsList.Front().Value.(formulaArg).Value()
-	return newStringFormulaArg(strings.Replace(url.QueryEscape(token), "+", "%20", -1))
-}
-
-// Financial Functions
-
-// CUMIPMT function calculates the cumulative interest paid on a loan or
-// investment, between two specified periods. The syntax of the function is:
-//
-//    CUMIPMT(rate,nper,pv,start_period,end_period,type)
-//
-func (fn *formulaFuncs) CUMIPMT(argsList *list.List) formulaArg {
-	return fn.cumip("CUMIPMT", argsList)
-}
-
-// CUMPRINC function calculates the cumulative payment on the principal of a
-// loan or investment, between two specified periods. The syntax of the
-// function is:
-//
-//    CUMPRINC(rate,nper,pv,start_period,end_period,type)
-//
-func (fn *formulaFuncs) CUMPRINC(argsList *list.List) formulaArg {
-	return fn.cumip("CUMPRINC", argsList)
-}
-
-// cumip is an implementation of the formula function CUMIPMT and CUMPRINC.
-func (fn *formulaFuncs) cumip(name string, argsList *list.List) formulaArg {
-	if argsList.Len() != 6 {
-		return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 6 arguments", name))
-	}
-	rate := argsList.Front().Value.(formulaArg).ToNumber()
-	if rate.Type != ArgNumber {
-		return rate
-	}
-	nper := argsList.Front().Next().Value.(formulaArg).ToNumber()
-	if nper.Type != ArgNumber {
-		return nper
-	}
-	pv := argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
-	if pv.Type != ArgNumber {
-		return pv
-	}
-	start := argsList.Back().Prev().Prev().Value.(formulaArg).ToNumber()
-	if start.Type != ArgNumber {
-		return start
-	}
-	end := argsList.Back().Prev().Value.(formulaArg).ToNumber()
-	if end.Type != ArgNumber {
-		return end
-	}
-	typ := argsList.Back().Value.(formulaArg).ToNumber()
-	if typ.Type != ArgNumber {
-		return typ
-	}
-	if typ.Number != 0 && typ.Number != 1 {
-		return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
-	}
-	if start.Number < 1 || start.Number > end.Number {
-		return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
-	}
-	num, ipmt := 0.0, newNumberFormulaArg(0)
-	for per := start.Number; per <= end.Number; per++ {
-		args := list.New().Init()
-		args.PushBack(rate)
-		args.PushBack(newNumberFormulaArg(per))
-		args.PushBack(nper)
-		args.PushBack(pv)
-		args.PushBack(newNumberFormulaArg(0))
-		args.PushBack(typ)
-		if name == "CUMIPMT" {
-			ipmt = fn.IPMT(args)
-		} else {
-			ipmt = fn.PPMT(args)
-		}
-		num += ipmt.Number
-	}
-	return newNumberFormulaArg(num)
-}
-
-// DB function calculates the depreciation of an asset, using the Fixed
-// Declining Balance Method, for each period of the asset's lifetime. The
-// syntax of the function is:
-//
-//    DB(cost,salvage,life,period,[month])
-//
-func (fn *formulaFuncs) DB(argsList *list.List) formulaArg {
-	if argsList.Len() < 4 {
-		return newErrorFormulaArg(formulaErrorVALUE, "DB requires at least 4 arguments")
-	}
-	if argsList.Len() > 5 {
-		return newErrorFormulaArg(formulaErrorVALUE, "DB allows at most 5 arguments")
-	}
-	cost := argsList.Front().Value.(formulaArg).ToNumber()
-	if cost.Type != ArgNumber {
-		return cost
-	}
-	salvage := argsList.Front().Next().Value.(formulaArg).ToNumber()
-	if salvage.Type != ArgNumber {
-		return salvage
-	}
-	life := argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
-	if life.Type != ArgNumber {
-		return life
-	}
-	period := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber()
-	if period.Type != ArgNumber {
-		return period
-	}
-	month := newNumberFormulaArg(12)
-	if argsList.Len() == 5 {
-		if month = argsList.Back().Value.(formulaArg).ToNumber(); month.Type != ArgNumber {
-			return month
-		}
-	}
-	if cost.Number == 0 {
-		return newNumberFormulaArg(0)
-	}
-	if (cost.Number <= 0) || ((salvage.Number / cost.Number) < 0) || (life.Number <= 0) || (period.Number < 1) || (month.Number < 1) {
-		return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
-	}
-	dr := 1 - math.Pow(salvage.Number/cost.Number, 1/life.Number)
-	dr = math.Round(dr*1000) / 1000
-	pd, depreciation := 0.0, 0.0
-	for per := 1; per <= int(period.Number); per++ {
-		if per == 1 {
-			depreciation = cost.Number * dr * month.Number / 12
-		} else if per == int(life.Number+1) {
-			depreciation = (cost.Number - pd) * dr * (12 - month.Number) / 12
-		} else {
-			depreciation = (cost.Number - pd) * dr
-		}
-		pd += depreciation
-	}
-	return newNumberFormulaArg(depreciation)
-}
-
-// DDB function calculates the depreciation of an asset, using the Double
-// Declining Balance Method, or another specified depreciation rate. The
-// syntax of the function is:
-//
-//    DDB(cost,salvage,life,period,[factor])
-//
-func (fn *formulaFuncs) DDB(argsList *list.List) formulaArg {
-	if argsList.Len() < 4 {
-		return newErrorFormulaArg(formulaErrorVALUE, "DDB requires at least 4 arguments")
-	}
-	if argsList.Len() > 5 {
-		return newErrorFormulaArg(formulaErrorVALUE, "DDB allows at most 5 arguments")
-	}
-	cost := argsList.Front().Value.(formulaArg).ToNumber()
-	if cost.Type != ArgNumber {
-		return cost
-	}
-	salvage := argsList.Front().Next().Value.(formulaArg).ToNumber()
-	if salvage.Type != ArgNumber {
-		return salvage
-	}
-	life := argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
-	if life.Type != ArgNumber {
-		return life
-	}
-	period := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber()
-	if period.Type != ArgNumber {
-		return period
-	}
-	factor := newNumberFormulaArg(2)
-	if argsList.Len() == 5 {
-		if factor = argsList.Back().Value.(formulaArg).ToNumber(); factor.Type != ArgNumber {
-			return factor
-		}
-	}
-	if cost.Number == 0 {
-		return newNumberFormulaArg(0)
-	}
-	if (cost.Number <= 0) || ((salvage.Number / cost.Number) < 0) || (life.Number <= 0) || (period.Number < 1) || (factor.Number <= 0.0) || (period.Number > life.Number) {
-		return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
-	}
-	pd, depreciation := 0.0, 0.0
-	for per := 1; per <= int(period.Number); per++ {
-		depreciation = math.Min((cost.Number-pd)*(factor.Number/life.Number), (cost.Number - salvage.Number - pd))
-		pd += depreciation
-	}
-	return newNumberFormulaArg(depreciation)
-}
-
-// DOLLARDE function converts a dollar value in fractional notation, into a
-// dollar value expressed as a decimal. The syntax of the function is:
-//
-//    DOLLARDE(fractional_dollar,fraction)
-//
-func (fn *formulaFuncs) DOLLARDE(argsList *list.List) formulaArg {
-	return fn.dollar("DOLLARDE", argsList)
-}
-
-// DOLLARFR function converts a dollar value in decimal notation, into a
-// dollar value that is expressed in fractional notation. The syntax of the
-// function is:
-//
-//    DOLLARFR(decimal_dollar,fraction)
-//
-func (fn *formulaFuncs) DOLLARFR(argsList *list.List) formulaArg {
-	return fn.dollar("DOLLARFR", argsList)
-}
-
-// dollar is an implementation of the formula function DOLLARDE and DOLLARFR.
-func (fn *formulaFuncs) dollar(name string, argsList *list.List) formulaArg {
-	if argsList.Len() != 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 2 arguments", name))
-	}
-	dollar := argsList.Front().Value.(formulaArg).ToNumber()
-	if dollar.Type != ArgNumber {
-		return dollar
-	}
-	frac := argsList.Back().Value.(formulaArg).ToNumber()
-	if frac.Type != ArgNumber {
-		return frac
-	}
-	if frac.Number < 0 {
-		return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
-	}
-	if frac.Number == 0 {
-		return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV)
-	}
-	cents := math.Mod(dollar.Number, 1)
-	if name == "DOLLARDE" {
-		cents /= frac.Number
-		cents *= math.Pow(10, math.Ceil(math.Log10(frac.Number)))
-	} else {
-		cents *= frac.Number
-		cents *= math.Pow(10, -math.Ceil(math.Log10(frac.Number)))
-	}
-	return newNumberFormulaArg(math.Floor(dollar.Number) + cents)
-}
-
-// EFFECT function returns the effective annual interest rate for a given
-// nominal interest rate and number of compounding periods per year. The
-// syntax of the function is:
-//
-//    EFFECT(nominal_rate,npery)
-//
-func (fn *formulaFuncs) EFFECT(argsList *list.List) formulaArg {
-	if argsList.Len() != 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "EFFECT requires 2 arguments")
-	}
-	rate := argsList.Front().Value.(formulaArg).ToNumber()
-	if rate.Type != ArgNumber {
-		return rate
-	}
-	npery := argsList.Back().Value.(formulaArg).ToNumber()
-	if npery.Type != ArgNumber {
-		return npery
-	}
-	if rate.Number <= 0 || npery.Number < 1 {
-		return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
-	}
-	return newNumberFormulaArg(math.Pow((1+rate.Number/npery.Number), npery.Number) - 1)
-}
-
-// FV function calculates the Future Value of an investment with periodic
-// constant payments and a constant interest rate. The syntax of the function
-// is:
-//
-//    FV(rate,nper,[pmt],[pv],[type])
-//
-func (fn *formulaFuncs) FV(argsList *list.List) formulaArg {
-	if argsList.Len() < 3 {
-		return newErrorFormulaArg(formulaErrorVALUE, "FV requires at least 3 arguments")
-	}
-	if argsList.Len() > 5 {
-		return newErrorFormulaArg(formulaErrorVALUE, "FV allows at most 5 arguments")
-	}
-	rate := argsList.Front().Value.(formulaArg).ToNumber()
-	if rate.Type != ArgNumber {
-		return rate
-	}
-	nper := argsList.Front().Next().Value.(formulaArg).ToNumber()
-	if nper.Type != ArgNumber {
-		return nper
-	}
-	pmt := argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
-	if pmt.Type != ArgNumber {
-		return pmt
-	}
-	pv, typ := newNumberFormulaArg(0), newNumberFormulaArg(0)
-	if argsList.Len() >= 4 {
-		if pv = argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber(); pv.Type != ArgNumber {
-			return pv
-		}
-	}
-	if argsList.Len() == 5 {
-		if typ = argsList.Back().Value.(formulaArg).ToNumber(); typ.Type != ArgNumber {
-			return typ
-		}
-	}
-	if typ.Number != 0 && typ.Number != 1 {
-		return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
-	}
-	if rate.Number != 0 {
-		return newNumberFormulaArg(-pv.Number*math.Pow(1+rate.Number, nper.Number) - pmt.Number*(1+rate.Number*typ.Number)*(math.Pow(1+rate.Number, nper.Number)-1)/rate.Number)
-	}
-	return newNumberFormulaArg(-pv.Number - pmt.Number*nper.Number)
-}
-
-// FVSCHEDULE function calculates the Future Value of an investment with a
-// variable interest rate. The syntax of the function is:
-//
-//    FVSCHEDULE(principal,schedule)
-//
-func (fn *formulaFuncs) FVSCHEDULE(argsList *list.List) formulaArg {
-	if argsList.Len() != 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "FVSCHEDULE requires 2 arguments")
-	}
-	pri := argsList.Front().Value.(formulaArg).ToNumber()
-	if pri.Type != ArgNumber {
-		return pri
-	}
-	principal := pri.Number
-	for _, arg := range argsList.Back().Value.(formulaArg).ToList() {
-		if arg.Value() == "" {
-			continue
-		}
-		rate := arg.ToNumber()
-		if rate.Type != ArgNumber {
-			return rate
-		}
-		principal *= (1 + rate.Number)
-	}
-	return newNumberFormulaArg(principal)
-}
-
-// IPMT function calculates the interest payment, during a specific period of a
-// loan or investment that is paid in constant periodic payments, with a
-// constant interest rate. The syntax of the function is:
-//
-//    IPMT(rate,per,nper,pv,[fv],[type])
-//
-func (fn *formulaFuncs) IPMT(argsList *list.List) formulaArg {
-	return fn.ipmt("IPMT", argsList)
-}
-
-// ipmt is an implementation of the formula function IPMT and PPMT.
-func (fn *formulaFuncs) ipmt(name string, argsList *list.List) formulaArg {
-	if argsList.Len() < 4 {
-		return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at least 4 arguments", name))
-	}
-	if argsList.Len() > 6 {
-		return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s allows at most 6 arguments", name))
-	}
-	rate := argsList.Front().Value.(formulaArg).ToNumber()
-	if rate.Type != ArgNumber {
-		return rate
-	}
-	per := argsList.Front().Next().Value.(formulaArg).ToNumber()
-	if per.Type != ArgNumber {
-		return per
-	}
-	nper := argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
-	if nper.Type != ArgNumber {
-		return nper
-	}
-	pv := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber()
-	if pv.Type != ArgNumber {
-		return pv
-	}
-	fv, typ := newNumberFormulaArg(0), newNumberFormulaArg(0)
-	if argsList.Len() >= 5 {
-		if fv = argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber(); fv.Type != ArgNumber {
-			return fv
-		}
-	}
-	if argsList.Len() == 6 {
-		if typ = argsList.Back().Value.(formulaArg).ToNumber(); typ.Type != ArgNumber {
-			return typ
-		}
-	}
-	if typ.Number != 0 && typ.Number != 1 {
-		return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
-	}
-	if per.Number <= 0 || per.Number > nper.Number {
-		return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
-	}
-	args := list.New().Init()
-	args.PushBack(rate)
-	args.PushBack(nper)
-	args.PushBack(pv)
-	args.PushBack(fv)
-	args.PushBack(typ)
-	pmt, capital, interest, principal := fn.PMT(args), pv.Number, 0.0, 0.0
-	for i := 1; i <= int(per.Number); i++ {
-		if typ.Number != 0 && i == 1 {
-			interest = 0
-		} else {
-			interest = -capital * rate.Number
-		}
-		principal = pmt.Number - interest
-		capital += principal
-	}
-	if name == "IPMT" {
-		return newNumberFormulaArg(interest)
-	}
-	return newNumberFormulaArg(principal)
-}
-
-// IRR function returns the Internal Rate of Return for a supplied series of
-// periodic cash flows (i.e. an initial investment value and a series of net
-// income values). The syntax of the function is:
-//
-//    IRR(values,[guess])
-//
-func (fn *formulaFuncs) IRR(argsList *list.List) formulaArg {
-	if argsList.Len() < 1 {
-		return newErrorFormulaArg(formulaErrorVALUE, "IRR requires at least 1 argument")
-	}
-	if argsList.Len() > 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "IRR allows at most 2 arguments")
-	}
-	values, guess := argsList.Front().Value.(formulaArg).ToList(), newNumberFormulaArg(0.1)
-	if argsList.Len() > 1 {
-		if guess = argsList.Back().Value.(formulaArg).ToNumber(); guess.Type != ArgNumber {
-			return guess
-		}
-	}
-	x1, x2 := newNumberFormulaArg(0), guess
-	args := list.New().Init()
-	args.PushBack(x1)
-	for _, v := range values {
-		args.PushBack(v)
-	}
-	f1 := fn.NPV(args)
-	args.Front().Value = x2
-	f2 := fn.NPV(args)
-	for i := 0; i < maxFinancialIterations; i++ {
-		if f1.Number*f2.Number < 0 {
-			break
-		}
-		if math.Abs(f1.Number) < math.Abs((f2.Number)) {
-			x1.Number += 1.6 * (x1.Number - x2.Number)
-			args.Front().Value = x1
-			f1 = fn.NPV(args)
-			continue
-		}
-		x2.Number += 1.6 * (x2.Number - x1.Number)
-		args.Front().Value = x2
-		f2 = fn.NPV(args)
-	}
-	if f1.Number*f2.Number > 0 {
-		return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
-	}
-	args.Front().Value = x1
-	f := fn.NPV(args)
-	var rtb, dx, xMid, fMid float64
-	if f.Number < 0 {
-		rtb = x1.Number
-		dx = x2.Number - x1.Number
-	} else {
-		rtb = x2.Number
-		dx = x1.Number - x2.Number
-	}
-	for i := 0; i < maxFinancialIterations; i++ {
-		dx *= 0.5
-		xMid = rtb + dx
-		args.Front().Value = newNumberFormulaArg(xMid)
-		fMid = fn.NPV(args).Number
-		if fMid <= 0 {
-			rtb = xMid
-		}
-		if math.Abs(fMid) < financialPercision || math.Abs(dx) < financialPercision {
-			break
-		}
-	}
-	return newNumberFormulaArg(xMid)
-}
-
-// ISPMT function calculates the interest paid during a specific period of a
-// loan or investment. The syntax of the function is:
-//
-//    ISPMT(rate,per,nper,pv)
-//
-func (fn *formulaFuncs) ISPMT(argsList *list.List) formulaArg {
-	if argsList.Len() != 4 {
-		return newErrorFormulaArg(formulaErrorVALUE, "ISPMT requires 4 arguments")
-	}
-	rate := argsList.Front().Value.(formulaArg).ToNumber()
-	if rate.Type != ArgNumber {
-		return rate
-	}
-	per := argsList.Front().Next().Value.(formulaArg).ToNumber()
-	if per.Type != ArgNumber {
-		return per
-	}
-	nper := argsList.Back().Prev().Value.(formulaArg).ToNumber()
-	if nper.Type != ArgNumber {
-		return nper
-	}
-	pv := argsList.Back().Value.(formulaArg).ToNumber()
-	if pv.Type != ArgNumber {
-		return pv
-	}
-	pr, payment, num := pv.Number, pv.Number/nper.Number, 0.0
-	for i := 0; i <= int(per.Number); i++ {
-		num = rate.Number * pr * -1
-		pr -= payment
-		if i == int(nper.Number) {
-			num = 0
-		}
-	}
-	return newNumberFormulaArg(num)
-}
-
-// MIRR function returns the Modified Internal Rate of Return for a supplied
-// series of periodic cash flows (i.e. a set of values, which includes an
-// initial investment value and a series of net income values). The syntax of
-// the function is:
-//
-//    MIRR(values,finance_rate,reinvest_rate)
-//
-func (fn *formulaFuncs) MIRR(argsList *list.List) formulaArg {
-	if argsList.Len() != 3 {
-		return newErrorFormulaArg(formulaErrorVALUE, "MIRR requires 3 arguments")
-	}
-	values := argsList.Front().Value.(formulaArg).ToList()
-	financeRate := argsList.Front().Next().Value.(formulaArg).ToNumber()
-	if financeRate.Type != ArgNumber {
-		return financeRate
-	}
-	reinvestRate := argsList.Back().Value.(formulaArg).ToNumber()
-	if reinvestRate.Type != ArgNumber {
-		return reinvestRate
-	}
-	n, fr, rr, npvPos, npvNeg := len(values), 1+financeRate.Number, 1+reinvestRate.Number, 0.0, 0.0
-	for i, v := range values {
-		val := v.ToNumber()
-		if val.Number >= 0 {
-			npvPos += val.Number / math.Pow(float64(rr), float64(i))
-			continue
-		}
-		npvNeg += val.Number / math.Pow(float64(fr), float64(i))
-	}
-	if npvNeg == 0 || npvPos == 0 || reinvestRate.Number <= -1 {
-		return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV)
-	}
-	return newNumberFormulaArg(math.Pow(-npvPos*math.Pow(rr, float64(n))/(npvNeg*rr), 1/(float64(n)-1)) - 1)
-}
-
-// NOMINAL function returns the nominal interest rate for a given effective
-// interest rate and number of compounding periods per year. The syntax of
-// the function is:
-//
-//    NOMINAL(effect_rate,npery)
-//
-func (fn *formulaFuncs) NOMINAL(argsList *list.List) formulaArg {
-	if argsList.Len() != 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "NOMINAL requires 2 arguments")
-	}
-	rate := argsList.Front().Value.(formulaArg).ToNumber()
-	if rate.Type != ArgNumber {
-		return rate
-	}
-	npery := argsList.Back().Value.(formulaArg).ToNumber()
-	if npery.Type != ArgNumber {
-		return npery
-	}
-	if rate.Number <= 0 || npery.Number < 1 {
-		return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
-	}
-	return newNumberFormulaArg(npery.Number * (math.Pow(rate.Number+1, 1/npery.Number) - 1))
-}
-
-// NPER function calculates the number of periods required to pay off a loan,
-// for a constant periodic payment and a constant interest rate. The syntax
-// of the function is:
-//
-//    NPER(rate,pmt,pv,[fv],[type])
-//
-func (fn *formulaFuncs) NPER(argsList *list.List) formulaArg {
-	if argsList.Len() < 3 {
-		return newErrorFormulaArg(formulaErrorVALUE, "NPER requires at least 3 arguments")
-	}
-	if argsList.Len() > 5 {
-		return newErrorFormulaArg(formulaErrorVALUE, "NPER allows at most 5 arguments")
-	}
-	rate := argsList.Front().Value.(formulaArg).ToNumber()
-	if rate.Type != ArgNumber {
-		return rate
-	}
-	pmt := argsList.Front().Next().Value.(formulaArg).ToNumber()
-	if pmt.Type != ArgNumber {
-		return pmt
-	}
-	pv := argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
-	if pv.Type != ArgNumber {
-		return pv
-	}
-	fv, typ := newNumberFormulaArg(0), newNumberFormulaArg(0)
-	if argsList.Len() >= 4 {
-		if fv = argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber(); fv.Type != ArgNumber {
-			return fv
-		}
-	}
-	if argsList.Len() == 5 {
-		if typ = argsList.Back().Value.(formulaArg).ToNumber(); typ.Type != ArgNumber {
-			return typ
-		}
-	}
-	if typ.Number != 0 && typ.Number != 1 {
-		return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
-	}
-	if pmt.Number == 0 {
-		return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
-	}
-	if rate.Number != 0 {
-		p := math.Log((pmt.Number*(1+rate.Number*typ.Number)/rate.Number-fv.Number)/(pv.Number+pmt.Number*(1+rate.Number*typ.Number)/rate.Number)) / math.Log(1+rate.Number)
-		return newNumberFormulaArg(p)
-	}
-	return newNumberFormulaArg((-pv.Number - fv.Number) / pmt.Number)
-}
-
-// NPV function calculates the Net Present Value of an investment, based on a
-// supplied discount rate, and a series of future payments and income. The
-// syntax of the function is:
-//
-//    NPV(rate,value1,[value2],[value3],...)
-//
-func (fn *formulaFuncs) NPV(argsList *list.List) formulaArg {
-	if argsList.Len() < 2 {
-		return newErrorFormulaArg(formulaErrorVALUE, "NPV requires at least 2 arguments")
-	}
-	rate := argsList.Front().Value.(formulaArg).ToNumber()
-	if rate.Type != ArgNumber {
-		return rate
-	}
-	val, i := 0.0, 1
-	for arg := argsList.Front().Next(); arg != nil; arg = arg.Next() {
-		num := arg.Value.(formulaArg).ToNumber()
-		if num.Type != ArgNumber {
-			continue
-		}
-		val += num.Number / math.Pow(1+rate.Number, float64(i))
-		i++
-	}
-	return newNumberFormulaArg(val)
-}
-
-// PDURATION function calculates the number of periods required for an
-// investment to reach a specified future value. The syntax of the function
-// is:
-//
-//    PDURATION(rate,pv,fv)
-//
-func (fn *formulaFuncs) PDURATION(argsList *list.List) formulaArg {
-	if argsList.Len() != 3 {
-		return newErrorFormulaArg(formulaErrorVALUE, "PDURATION requires 3 arguments")
-	}
-	rate := argsList.Front().Value.(formulaArg).ToNumber()
-	if rate.Type != ArgNumber {
-		return rate
-	}
-	pv := argsList.Front().Next().Value.(formulaArg).ToNumber()
-	if pv.Type != ArgNumber {
-		return pv
-	}
-	fv := argsList.Back().Value.(formulaArg).ToNumber()
-	if fv.Type != ArgNumber {
-		return fv
-	}
-	if rate.Number <= 0 || pv.Number <= 0 || fv.Number <= 0 {
-		return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
-	}
-	return newNumberFormulaArg((math.Log(fv.Number) - math.Log(pv.Number)) / math.Log(1+rate.Number))
-}
-
-// PMT function calculates the constant periodic payment required to pay off
-// (or partially pay off) a loan or investment, with a constant interest
-// rate, over a specified period. The syntax of the function is:
-//
-//    PMT(rate,nper,pv,[fv],[type])
-//
-func (fn *formulaFuncs) PMT(argsList *list.List) formulaArg {
-	if argsList.Len() < 3 {
-		return newErrorFormulaArg(formulaErrorVALUE, "PMT requires at least 3 arguments")
-	}
-	if argsList.Len() > 5 {
-		return newErrorFormulaArg(formulaErrorVALUE, "PMT allows at most 5 arguments")
-	}
-	rate := argsList.Front().Value.(formulaArg).ToNumber()
-	if rate.Type != ArgNumber {
-		return rate
-	}
-	nper := argsList.Front().Next().Value.(formulaArg).ToNumber()
-	if nper.Type != ArgNumber {
-		return nper
-	}
-	pv := argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
-	if pv.Type != ArgNumber {
-		return pv
-	}
-	fv, typ := newNumberFormulaArg(0), newNumberFormulaArg(0)
-	if argsList.Len() >= 4 {
-		if fv = argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber(); fv.Type != ArgNumber {
-			return fv
-		}
-	}
-	if argsList.Len() == 5 {
-		if typ = argsList.Back().Value.(formulaArg).ToNumber(); typ.Type != ArgNumber {
-			return typ
-		}
-	}
-	if typ.Number != 0 && typ.Number != 1 {
-		return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
-	}
-	if rate.Number != 0 {
-		p := (-fv.Number - pv.Number*math.Pow((1+rate.Number), nper.Number)) / (1 + rate.Number*typ.Number) / ((math.Pow((1+rate.Number), nper.Number) - 1) / rate.Number)
-		return newNumberFormulaArg(p)
-	}
-	return newNumberFormulaArg((-pv.Number - fv.Number) / nper.Number)
-}
-
-// PPMT function calculates the payment on the principal, during a specific
-// period of a loan or investment that is paid in constant periodic payments,
-// with a constant interest rate. The syntax of the function is:
-//
-//    PPMT(rate,per,nper,pv,[fv],[type])
-//
-func (fn *formulaFuncs) PPMT(argsList *list.List) formulaArg {
-	return fn.ipmt("PPMT", argsList)
-}

+ 0 - 2597
calc_test.go

@@ -1,2597 +0,0 @@
-package excelize
-
-import (
-	"container/list"
-	"path/filepath"
-	"strings"
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-	"github.com/xuri/efp"
-)
-
-func prepareCalcData(cellData [][]interface{}) *File {
-	f := NewFile()
-	for r, row := range cellData {
-		for c, value := range row {
-			cell, _ := CoordinatesToCellName(c+1, r+1)
-			_ = f.SetCellValue("Sheet1", cell, value)
-		}
-	}
-	return f
-}
-
-func TestCalcCellValue(t *testing.T) {
-	cellData := [][]interface{}{
-		{1, 4, nil, "Month", "Team", "Sales"},
-		{2, 5, nil, "Jan", "North 1", 36693},
-		{3, nil, nil, "Jan", "North 2", 22100},
-		{0, nil, nil, "Jan", "South 1", 53321},
-		{nil, nil, nil, "Jan", "South 2", 34440},
-		{nil, nil, nil, "Feb", "North 1", 29889},
-		{nil, nil, nil, "Feb", "North 2", 50090},
-		{nil, nil, nil, "Feb", "South 1", 32080},
-		{nil, nil, nil, "Feb", "South 2", 45500},
-	}
-	mathCalc := map[string]string{
-		"=2^3":  "8",
-		"=1=1":  "TRUE",
-		"=1=2":  "FALSE",
-		"=1<2":  "TRUE",
-		"=3<2":  "FALSE",
-		"=2<=3": "TRUE",
-		"=2<=1": "FALSE",
-		"=2>1":  "TRUE",
-		"=2>3":  "FALSE",
-		"=2>=1": "TRUE",
-		"=2>=3": "FALSE",
-		"=1&2":  "12",
-		// Engineering Functions
-		// BESSELI
-		"=BESSELI(4.5,1)": "15.389222753735925",
-		"=BESSELI(32,1)":  "5.502845511211247e+12",
-		// BESSELJ
-		"=BESSELJ(1.9,2)": "0.329925727692387",
-		// BESSELK
-		"=BESSELK(0.05,0)": "3.114234034289662",
-		"=BESSELK(0.05,1)": "19.90967432724863",
-		"=BESSELK(0.05,2)": "799.501207124235",
-		"=BESSELK(3,2)":    "0.061510458561912",
-		// BESSELY
-		"=BESSELY(0.05,0)": "-1.979311006841528",
-		"=BESSELY(0.05,1)": "-12.789855163794034",
-		"=BESSELY(0.05,2)": "-509.61489554491976",
-		"=BESSELY(9,2)":    "-0.229082087487741",
-		// BIN2DEC
-		"=BIN2DEC(\"10\")":         "2",
-		"=BIN2DEC(\"11\")":         "3",
-		"=BIN2DEC(\"0000000010\")": "2",
-		"=BIN2DEC(\"1111111110\")": "-2",
-		"=BIN2DEC(\"110\")":        "6",
-		// BIN2HEX
-		"=BIN2HEX(\"10\")":         "2",
-		"=BIN2HEX(\"0000000001\")": "1",
-		"=BIN2HEX(\"10\",10)":      "0000000002",
-		"=BIN2HEX(\"1111111110\")": "FFFFFFFFFE",
-		"=BIN2HEX(\"11101\")":      "1D",
-		// BIN2OCT
-		"=BIN2OCT(\"101\")":        "5",
-		"=BIN2OCT(\"0000000001\")": "1",
-		"=BIN2OCT(\"10\",10)":      "0000000002",
-		"=BIN2OCT(\"1111111110\")": "7777777776",
-		"=BIN2OCT(\"1110\")":       "16",
-		// BITAND
-		"=BITAND(13,14)": "12",
-		// BITLSHIFT
-		"=BITLSHIFT(5,2)": "20",
-		"=BITLSHIFT(3,5)": "96",
-		// BITOR
-		"=BITOR(9,12)": "13",
-		// BITRSHIFT
-		"=BITRSHIFT(20,2)": "5",
-		"=BITRSHIFT(52,4)": "3",
-		// BITXOR
-		"=BITXOR(5,6)":  "3",
-		"=BITXOR(9,12)": "5",
-		// COMPLEX
-		"=COMPLEX(5,2)":         "5+2i",
-		"=COMPLEX(5,-9)":        "5-9i",
-		"=COMPLEX(-1,2,\"j\")":  "-1+2j",
-		"=COMPLEX(10,-5,\"i\")": "10-5i",
-		"=COMPLEX(0,5)":         "5i",
-		"=COMPLEX(3,0)":         "3",
-		"=COMPLEX(0,-2)":        "-2i",
-		"=COMPLEX(0,0)":         "0",
-		"=COMPLEX(0,-1,\"j\")":  "-j",
-		// DEC2BIN
-		"=DEC2BIN(2)":    "10",
-		"=DEC2BIN(3)":    "11",
-		"=DEC2BIN(2,10)": "0000000010",
-		"=DEC2BIN(-2)":   "1111111110",
-		"=DEC2BIN(6)":    "110",
-		// DEC2HEX
-		"=DEC2HEX(10)":    "A",
-		"=DEC2HEX(31)":    "1F",
-		"=DEC2HEX(16,10)": "0000000010",
-		"=DEC2HEX(-16)":   "FFFFFFFFF0",
-		"=DEC2HEX(273)":   "111",
-		// DEC2OCT
-		"=DEC2OCT(8)":    "10",
-		"=DEC2OCT(18)":   "22",
-		"=DEC2OCT(8,10)": "0000000010",
-		"=DEC2OCT(-8)":   "7777777770",
-		"=DEC2OCT(237)":  "355",
-		// HEX2BIN
-		"=HEX2BIN(\"2\")":          "10",
-		"=HEX2BIN(\"0000000001\")": "1",
-		"=HEX2BIN(\"2\",10)":       "0000000010",
-		"=HEX2BIN(\"F0\")":         "11110000",
-		"=HEX2BIN(\"1D\")":         "11101",
-		// HEX2DEC
-		"=HEX2DEC(\"A\")":          "10",
-		"=HEX2DEC(\"1F\")":         "31",
-		"=HEX2DEC(\"0000000010\")": "16",
-		"=HEX2DEC(\"FFFFFFFFF0\")": "-16",
-		"=HEX2DEC(\"111\")":        "273",
-		"=HEX2DEC(\"\")":           "0",
-		// HEX2OCT
-		"=HEX2OCT(\"A\")":          "12",
-		"=HEX2OCT(\"000000000F\")": "17",
-		"=HEX2OCT(\"8\",10)":       "0000000010",
-		"=HEX2OCT(\"FFFFFFFFF8\")": "7777777770",
-		"=HEX2OCT(\"1F3\")":        "763",
-		// IMABS
-		"=IMABS(\"2j\")":              "2",
-		"=IMABS(\"-1+2i\")":           "2.23606797749979",
-		"=IMABS(COMPLEX(-1,2,\"j\"))": "2.23606797749979",
-		// IMAGINARY
-		"=IMAGINARY(\"5+2i\")": "2",
-		"=IMAGINARY(\"2-i\")":  "-1",
-		"=IMAGINARY(6)":        "0",
-		"=IMAGINARY(\"3i\")":   "3",
-		"=IMAGINARY(\"4+i\")":  "1",
-		// IMARGUMENT
-		"=IMARGUMENT(\"5+2i\")": "0.380506377112365",
-		"=IMARGUMENT(\"2-i\")":  "-0.463647609000806",
-		"=IMARGUMENT(6)":        "0",
-		// IMCONJUGATE
-		"=IMCONJUGATE(\"5+2i\")": "5-2i",
-		"=IMCONJUGATE(\"2-i\")":  "2+i",
-		"=IMCONJUGATE(6)":        "6",
-		"=IMCONJUGATE(\"3i\")":   "-3i",
-		"=IMCONJUGATE(\"4+i\")":  "4-i",
-		// IMCOS
-		"=IMCOS(0)":          "1",
-		"=IMCOS(0.5)":        "0.877582561890373",
-		"=IMCOS(\"3+0.5i\")": "-1.1163412445261518-0.0735369737112366i",
-		// IMCOSH
-		"=IMCOSH(0.5)":           "1.127625965206381",
-		"=IMCOSH(\"3+0.5i\")":    "8.835204606500994+4.802825082743033i",
-		"=IMCOSH(\"2-i\")":       "2.0327230070196656-3.0518977991518i",
-		"=IMCOSH(COMPLEX(1,-1))": "0.8337300251311491-0.9888977057628651i",
-		// IMCOT
-		"=IMCOT(0.5)":           "1.830487721712452",
-		"=IMCOT(\"3+0.5i\")":    "-0.4793455787473728-2.016092521506228i",
-		"=IMCOT(\"2-i\")":       "-0.171383612909185+0.8213297974938518i",
-		"=IMCOT(COMPLEX(1,-1))": "0.21762156185440268+0.868014142895925i",
-		// IMCSC
-		"=IMCSC(\"j\")": "-0.8509181282393216i",
-		// IMCSCH
-		"=IMCSCH(COMPLEX(1,-1))": "0.30393100162842646+0.6215180171704284i",
-		// IMDIV
-		"=IMDIV(\"5+2i\",\"1+i\")":          "3.5-1.5i",
-		"=IMDIV(\"2+2i\",\"2+i\")":          "1.2+0.4i",
-		"=IMDIV(COMPLEX(5,2),COMPLEX(0,1))": "2-5i",
-		// IMEXP
-		"=IMEXP(0)":             "1",
-		"=IMEXP(0.5)":           "1.648721270700128",
-		"=IMEXP(\"1-2i\")":      "-1.1312043837568135-2.4717266720048183i",
-		"=IMEXP(COMPLEX(1,-1))": "1.4686939399158851-2.2873552871788423i",
-		// IMLN
-		"=IMLN(0.5)":           "-0.693147180559945",
-		"=IMLN(\"3+0.5i\")":    "1.1123117757621668+0.16514867741462683i",
-		"=IMLN(\"2-i\")":       "0.8047189562170503-0.4636476090008061i",
-		"=IMLN(COMPLEX(1,-1))": "0.3465735902799727-0.7853981633974483i",
-		// IMLOG10
-		"=IMLOG10(0.5)":           "-0.301029995663981",
-		"=IMLOG10(\"3+0.5i\")":    "0.48307086636951624+0.07172315929479262i",
-		"=IMLOG10(\"2-i\")":       "0.34948500216800943-0.20135959813668655i",
-		"=IMLOG10(COMPLEX(1,-1))": "0.1505149978319906-0.3410940884604603i",
-		// IMREAL
-		"=IMREAL(\"5+2i\")":     "5",
-		"=IMREAL(\"2+2i\")":     "2",
-		"=IMREAL(6)":            "6",
-		"=IMREAL(\"3i\")":       "0",
-		"=IMREAL(COMPLEX(4,1))": "4",
-		// IMSEC
-		"=IMSEC(0.5)":           "1.139493927324549",
-		"=IMSEC(\"3+0.5i\")":    "-0.8919131797403304+0.05875317818173977i",
-		"=IMSEC(\"2-i\")":       "-0.4131493442669401-0.687527438655479i",
-		"=IMSEC(COMPLEX(1,-1))": "0.49833703055518686-0.5910838417210451i",
-		// IMSECH
-		"=IMSECH(0.5)":           "0.886818883970074",
-		"=IMSECH(\"3+0.5i\")":    "0.08736657796213027-0.047492549490160664i",
-		"=IMSECH(\"2-i\")":       "0.1511762982655772+0.22697367539372157i",
-		"=IMSECH(COMPLEX(1,-1))": "0.49833703055518686+0.5910838417210451i",
-		// IMSIN
-		"=IMSIN(0.5)":           "0.479425538604203",
-		"=IMSIN(\"3+0.5i\")":    "0.15913058529843999-0.5158804424525267i",
-		"=IMSIN(\"2-i\")":       "1.4031192506220405+0.4890562590412937i",
-		"=IMSIN(COMPLEX(1,-1))": "1.2984575814159773-0.6349639147847361i",
-		// IMSINH
-		"=IMSINH(-0)":            "0",
-		"=IMSINH(0.5)":           "0.521095305493747",
-		"=IMSINH(\"3+0.5i\")":    "8.791512343493714+4.82669427481082i",
-		"=IMSINH(\"2-i\")":       "1.9596010414216063-3.165778513216168i",
-		"=IMSINH(COMPLEX(1,-1))": "0.6349639147847361-1.2984575814159773i",
-		// IMSQRT
-		"=IMSQRT(\"i\")":     "0.7071067811865476+0.7071067811865476i",
-		"=IMSQRT(\"2-i\")":   "1.455346690225355-0.34356074972251244i",
-		"=IMSQRT(\"5+2i\")":  "2.27872385417085+0.4388421169022545i",
-		"=IMSQRT(6)":         "2.449489742783178",
-		"=IMSQRT(\"-2-4i\")": "1.1117859405028423-1.7989074399478673i",
-		// IMSUB
-		"=IMSUB(\"5+i\",\"1+4i\")":          "4-3i",
-		"=IMSUB(\"9+2i\",6)":                "3+2i",
-		"=IMSUB(COMPLEX(5,2),COMPLEX(0,1))": "5+i",
-		// IMSUM
-		"=IMSUM(\"1-i\",\"5+10i\",2)":       "8+9i",
-		"=IMSUM(COMPLEX(5,2),COMPLEX(0,1))": "5+3i",
-		// IMTAN
-		"=IMTAN(-0)":            "0",
-		"=IMTAN(0.5)":           "0.546302489843791",
-		"=IMTAN(\"3+0.5i\")":    "-0.11162105077158344+0.46946999342588536i",
-		"=IMTAN(\"2-i\")":       "-0.24345820118572523-1.16673625724092i",
-		"=IMTAN(COMPLEX(1,-1))": "0.2717525853195117-1.0839233273386948i",
-		// OCT2BIN
-		"=OCT2BIN(\"5\")":          "101",
-		"=OCT2BIN(\"0000000001\")": "1",
-		"=OCT2BIN(\"2\",10)":       "0000000010",
-		"=OCT2BIN(\"7777777770\")": "1111111000",
-		"=OCT2BIN(\"16\")":         "1110",
-		// OCT2DEC
-		"=OCT2DEC(\"10\")":         "8",
-		"=OCT2DEC(\"22\")":         "18",
-		"=OCT2DEC(\"0000000010\")": "8",
-		"=OCT2DEC(\"7777777770\")": "-8",
-		"=OCT2DEC(\"355\")":        "237",
-		// OCT2HEX
-		"=OCT2HEX(\"10\")":         "8",
-		"=OCT2HEX(\"0000000007\")": "7",
-		"=OCT2HEX(\"10\",10)":      "0000000008",
-		"=OCT2HEX(\"7777777770\")": "FFFFFFFFF8",
-		"=OCT2HEX(\"763\")":        "1F3",
-		// Math and Trigonometric Functions
-		// ABS
-		"=ABS(-1)":      "1",
-		"=ABS(-6.5)":    "6.5",
-		"=ABS(6.5)":     "6.5",
-		"=ABS(0)":       "0",
-		"=ABS(2-4.5)":   "2.5",
-		"=ABS(ABS(-1))": "1",
-		// ACOS
-		"=ACOS(-1)":     "3.141592653589793",
-		"=ACOS(0)":      "1.570796326794897",
-		"=ACOS(ABS(0))": "1.570796326794897",
-		// ACOSH
-		"=ACOSH(1)":        "0",
-		"=ACOSH(2.5)":      "1.566799236972411",
-		"=ACOSH(5)":        "2.292431669561178",
-		"=ACOSH(ACOSH(5))": "1.471383321536679",
-		// ACOT
-		"=_xlfn.ACOT(1)":             "0.785398163397448",
-		"=_xlfn.ACOT(-2)":            "2.677945044588987",
-		"=_xlfn.ACOT(0)":             "1.570796326794897",
-		"=_xlfn.ACOT(_xlfn.ACOT(0))": "0.566911504941009",
-		// ACOTH
-		"=_xlfn.ACOTH(-5)":      "-0.202732554054082",
-		"=_xlfn.ACOTH(1.1)":     "1.522261218861711",
-		"=_xlfn.ACOTH(2)":       "0.549306144334055",
-		"=_xlfn.ACOTH(ABS(-2))": "0.549306144334055",
-		// ARABIC
-		"=_xlfn.ARABIC(\"IV\")":       "4",
-		"=_xlfn.ARABIC(\"-IV\")":      "-4",
-		"=_xlfn.ARABIC(\"MCXX\")":     "1120",
-		"=_xlfn.ARABIC(\"\")":         "0",
-		"=_xlfn.ARABIC(\" ll  lc \")": "-50",
-		// ASIN
-		"=ASIN(-1)":      "-1.570796326794897",
-		"=ASIN(0)":       "0",
-		"=ASIN(ASIN(0))": "0",
-		// ASINH
-		"=ASINH(0)":        "0",
-		"=ASINH(-0.5)":     "-0.481211825059604",
-		"=ASINH(2)":        "1.44363547517881",
-		"=ASINH(ASINH(0))": "0",
-		// ATAN
-		"=ATAN(-1)":      "-0.785398163397448",
-		"=ATAN(0)":       "0",
-		"=ATAN(1)":       "0.785398163397448",
-		"=ATAN(ATAN(0))": "0",
-		// ATANH
-		"=ATANH(-0.8)":     "-1.09861228866811",
-		"=ATANH(0)":        "0",
-		"=ATANH(0.5)":      "0.549306144334055",
-		"=ATANH(ATANH(0))": "0",
-		// ATAN2
-		"=ATAN2(1,1)":          "0.785398163397448",
-		"=ATAN2(1,-1)":         "-0.785398163397448",
-		"=ATAN2(4,0)":          "0",
-		"=ATAN2(4,ATAN2(4,0))": "0",
-		// BASE
-		"=BASE(12,2)":          "1100",
-		"=BASE(12,2,8)":        "00001100",
-		"=BASE(100000,16)":     "186A0",
-		"=BASE(BASE(12,2),16)": "44C",
-		// CEILING
-		"=CEILING(22.25,0.1)":              "22.3",
-		"=CEILING(22.25,0.5)":              "22.5",
-		"=CEILING(22.25,1)":                "23",
-		"=CEILING(22.25,10)":               "30",
-		"=CEILING(22.25,20)":               "40",
-		"=CEILING(-22.25,-0.1)":            "-22.3",
-		"=CEILING(-22.25,-1)":              "-23",
-		"=CEILING(-22.25,-5)":              "-25",
-		"=CEILING(22.25)":                  "23",
-		"=CEILING(CEILING(22.25,0.1),0.1)": "22.3",
-		// _xlfn.CEILING.MATH
-		"=_xlfn.CEILING.MATH(15.25,1)":                       "16",
-		"=_xlfn.CEILING.MATH(15.25,0.1)":                     "15.3",
-		"=_xlfn.CEILING.MATH(15.25,5)":                       "20",
-		"=_xlfn.CEILING.MATH(-15.25,1)":                      "-15",
-		"=_xlfn.CEILING.MATH(-15.25,1,1)":                    "-15", // should be 16
-		"=_xlfn.CEILING.MATH(-15.25,10)":                     "-10",
-		"=_xlfn.CEILING.MATH(-15.25)":                        "-15",
-		"=_xlfn.CEILING.MATH(-15.25,-5,-1)":                  "-10",
-		"=_xlfn.CEILING.MATH(_xlfn.CEILING.MATH(15.25,1),1)": "16",
-		// _xlfn.CEILING.PRECISE
-		"=_xlfn.CEILING.PRECISE(22.25,0.1)":                          "22.3",
-		"=_xlfn.CEILING.PRECISE(22.25,0.5)":                          "22.5",
-		"=_xlfn.CEILING.PRECISE(22.25,1)":                            "23",
-		"=_xlfn.CEILING.PRECISE(22.25)":                              "23",
-		"=_xlfn.CEILING.PRECISE(22.25,10)":                           "30",
-		"=_xlfn.CEILING.PRECISE(22.25,0)":                            "0",
-		"=_xlfn.CEILING.PRECISE(-22.25,1)":                           "-22",
-		"=_xlfn.CEILING.PRECISE(-22.25,-1)":                          "-22",
-		"=_xlfn.CEILING.PRECISE(-22.25,5)":                           "-20",
-		"=_xlfn.CEILING.PRECISE(_xlfn.CEILING.PRECISE(22.25,0.1),5)": "25",
-		// COMBIN
-		"=COMBIN(6,1)":           "6",
-		"=COMBIN(6,2)":           "15",
-		"=COMBIN(6,3)":           "20",
-		"=COMBIN(6,4)":           "15",
-		"=COMBIN(6,5)":           "6",
-		"=COMBIN(6,6)":           "1",
-		"=COMBIN(0,0)":           "1",
-		"=COMBIN(6,COMBIN(0,0))": "6",
-		// _xlfn.COMBINA
-		"=_xlfn.COMBINA(6,1)":                  "6",
-		"=_xlfn.COMBINA(6,2)":                  "21",
-		"=_xlfn.COMBINA(6,3)":                  "56",
-		"=_xlfn.COMBINA(6,4)":                  "126",
-		"=_xlfn.COMBINA(6,5)":                  "252",
-		"=_xlfn.COMBINA(6,6)":                  "462",
-		"=_xlfn.COMBINA(0,0)":                  "0",
-		"=_xlfn.COMBINA(0,_xlfn.COMBINA(0,0))": "0",
-		// COS
-		"=COS(0.785398163)": "0.707106781467586",
-		"=COS(0)":           "1",
-		"=COS(COS(0))":      "0.54030230586814",
-		// COSH
-		"=COSH(0)":       "1",
-		"=COSH(0.5)":     "1.127625965206381",
-		"=COSH(-2)":      "3.762195691083632",
-		"=COSH(COSH(0))": "1.543080634815244",
-		// _xlfn.COT
-		"=_xlfn.COT(0.785398163397448)": "1.000000000000001",
-		"=_xlfn.COT(_xlfn.COT(0.45))":   "-0.545473116787229",
-		// _xlfn.COTH
-		"=_xlfn.COTH(-3.14159265358979)": "-1.003741873197322",
-		"=_xlfn.COTH(_xlfn.COTH(1))":     "1.156014018113954",
-		// _xlfn.CSC
-		"=_xlfn.CSC(-6)":              "3.578899547254406",
-		"=_xlfn.CSC(1.5707963267949)": "1",
-		"=_xlfn.CSC(_xlfn.CSC(1))":    "1.077851840310882",
-		// _xlfn.CSCH
-		"=_xlfn.CSCH(-3.14159265358979)": "-0.086589537530047",
-		"=_xlfn.CSCH(_xlfn.CSCH(1))":     "1.044510103955183",
-		// _xlfn.DECIMAL
-		`=_xlfn.DECIMAL("1100",2)`:    "12",
-		`=_xlfn.DECIMAL("186A0",16)`:  "100000",
-		`=_xlfn.DECIMAL("31L0",32)`:   "100000",
-		`=_xlfn.DECIMAL("70122",8)`:   "28754",
-		`=_xlfn.DECIMAL("0x70122",8)`: "28754",
-		// DEGREES
-		"=DEGREES(1)":          "57.29577951308232",
-		"=DEGREES(2.5)":        "143.2394487827058",
-		"=DEGREES(DEGREES(1))": "3282.806350011744",
-		// EVEN
-		"=EVEN(23)":   "24",
-		"=EVEN(2.22)": "4",
-		"=EVEN(0)":    "0",
-		"=EVEN(-0.3)": "-2",
-		"=EVEN(-11)":  "-12",
-		"=EVEN(-4)":   "-4",
-		"=EVEN((0))":  "0",
-		// EXP
-		"=EXP(100)":    "2.6881171418161356E+43",
-		"=EXP(0.1)":    "1.105170918075648",
-		"=EXP(0)":      "1",
-		"=EXP(-5)":     "0.006737946999085",
-		"=EXP(EXP(0))": "2.718281828459045",
-		// FACT
-		"=FACT(3)":       "6",
-		"=FACT(6)":       "720",
-		"=FACT(10)":      "3.6288e+06",
-		"=FACT(FACT(3))": "720",
-		// FACTDOUBLE
-		"=FACTDOUBLE(5)":             "15",
-		"=FACTDOUBLE(8)":             "384",
-		"=FACTDOUBLE(13)":            "135135",
-		"=FACTDOUBLE(FACTDOUBLE(1))": "1",
-		// FLOOR
-		"=FLOOR(26.75,0.1)":        "26.700000000000003",
-		"=FLOOR(26.75,0.5)":        "26.5",
-		"=FLOOR(26.75,1)":          "26",
-		"=FLOOR(26.75,10)":         "20",
-		"=FLOOR(26.75,20)":         "20",
-		"=FLOOR(-26.75,-0.1)":      "-26.700000000000003",
-		"=FLOOR(-26.75,-1)":        "-26",
-		"=FLOOR(-26.75,-5)":        "-25",
-		"=FLOOR(FLOOR(26.75,1),1)": "26",
-		// _xlfn.FLOOR.MATH
-		"=_xlfn.FLOOR.MATH(58.55)":                  "58",
-		"=_xlfn.FLOOR.MATH(58.55,0.1)":              "58.5",
-		"=_xlfn.FLOOR.MATH(58.55,5)":                "55",
-		"=_xlfn.FLOOR.MATH(58.55,1,1)":              "58",
-		"=_xlfn.FLOOR.MATH(-58.55,1)":               "-59",
-		"=_xlfn.FLOOR.MATH(-58.55,1,-1)":            "-58",
-		"=_xlfn.FLOOR.MATH(-58.55,1,1)":             "-59", // should be -58
-		"=_xlfn.FLOOR.MATH(-58.55,10)":              "-60",
-		"=_xlfn.FLOOR.MATH(_xlfn.FLOOR.MATH(1),10)": "0",
-		// _xlfn.FLOOR.PRECISE
-		"=_xlfn.FLOOR.PRECISE(26.75,0.1)":                     "26.700000000000003",
-		"=_xlfn.FLOOR.PRECISE(26.75,0.5)":                     "26.5",
-		"=_xlfn.FLOOR.PRECISE(26.75,1)":                       "26",
-		"=_xlfn.FLOOR.PRECISE(26.75)":                         "26",
-		"=_xlfn.FLOOR.PRECISE(26.75,10)":                      "20",
-		"=_xlfn.FLOOR.PRECISE(26.75,0)":                       "0",
-		"=_xlfn.FLOOR.PRECISE(-26.75,1)":                      "-27",
-		"=_xlfn.FLOOR.PRECISE(-26.75,-1)":                     "-27",
-		"=_xlfn.FLOOR.PRECISE(-26.75,-5)":                     "-30",
-		"=_xlfn.FLOOR.PRECISE(_xlfn.FLOOR.PRECISE(26.75),-5)": "25",
-		// GCD
-		"=GCD(0)":        "0",
-		"=GCD(1,0)":      "1",
-		"=GCD(1,5)":      "1",
-		"=GCD(15,10,25)": "5",
-		"=GCD(0,8,12)":   "4",
-		"=GCD(7,2)":      "1",
-		"=GCD(1,GCD(1))": "1",
-		// INT
-		"=INT(100.9)":  "100",
-		"=INT(5.22)":   "5",
-		"=INT(5.99)":   "5",
-		"=INT(-6.1)":   "-7",
-		"=INT(-100.9)": "-101",
-		"=INT(INT(0))": "0",
-		// ISO.CEILING
-		"=ISO.CEILING(22.25)":              "23",
-		"=ISO.CEILING(22.25,1)":            "23",
-		"=ISO.CEILING(22.25,0.1)":          "22.3",
-		"=ISO.CEILING(22.25,10)":           "30",
-		"=ISO.CEILING(-22.25,1)":           "-22",
-		"=ISO.CEILING(-22.25,0.1)":         "-22.200000000000003",
-		"=ISO.CEILING(-22.25,5)":           "-20",
-		"=ISO.CEILING(-22.25,0)":           "0",
-		"=ISO.CEILING(1,ISO.CEILING(1,0))": "0",
-		// LCM
-		"=LCM(1,5)":        "5",
-		"=LCM(15,10,25)":   "150",
-		"=LCM(1,8,12)":     "24",
-		"=LCM(7,2)":        "14",
-		"=LCM(7)":          "7",
-		`=LCM("",1)`:       "1",
-		`=LCM(0,0)`:        "0",
-		`=LCM(0,LCM(0,0))`: "0",
-		// LN
-		"=LN(1)":       "0",
-		"=LN(100)":     "4.605170185988092",
-		"=LN(0.5)":     "-0.693147180559945",
-		"=LN(LN(100))": "1.527179625807901",
-		// LOG
-		"=LOG(64,2)":     "6",
-		"=LOG(100)":      "2",
-		"=LOG(4,0.5)":    "-2",
-		"=LOG(500)":      "2.698970004336019",
-		"=LOG(LOG(100))": "0.301029995663981",
-		// LOG10
-		"=LOG10(100)":        "2",
-		"=LOG10(1000)":       "3",
-		"=LOG10(0.001)":      "-3",
-		"=LOG10(25)":         "1.397940008672038",
-		"=LOG10(LOG10(100))": "0.301029995663981",
-		// IMLOG2
-		"=IMLOG2(\"5+2i\")": "2.4289904975637864+0.5489546632866347i",
-		"=IMLOG2(\"2-i\")":  "1.1609640474436813-0.6689021062254881i",
-		"=IMLOG2(6)":        "2.584962500721156",
-		"=IMLOG2(\"3i\")":   "1.584962500721156+2.266180070913597i",
-		"=IMLOG2(\"4+i\")":  "2.04373142062517+0.3534295024167349i",
-		// IMPOWER
-		"=IMPOWER(\"2-i\",2)":   "3.000000000000001-4i",
-		"=IMPOWER(\"2-i\",3)":   "2.0000000000000018-11.000000000000002i",
-		"=IMPOWER(9,0.5)":       "3",
-		"=IMPOWER(\"2+4i\",-2)": "-0.029999999999999985-0.039999999999999994i",
-		// IMPRODUCT
-		"=IMPRODUCT(3,6)":                       "18",
-		`=IMPRODUCT("",3,SUM(6))`:               "18",
-		"=IMPRODUCT(\"1-i\",\"5+10i\",2)":       "30+10i",
-		"=IMPRODUCT(COMPLEX(5,2),COMPLEX(0,1))": "-2+5i",
-		"=IMPRODUCT(A1:C1)":                     "4",
-		// MOD
-		"=MOD(6,4)":        "2",
-		"=MOD(6,3)":        "0",
-		"=MOD(6,2.5)":      "1",
-		"=MOD(6,1.333)":    "0.668",
-		"=MOD(-10.23,1)":   "0.77",
-		"=MOD(MOD(1,1),1)": "0",
-		// MROUND
-		"=MROUND(333.7,0.5)":     "333.5",
-		"=MROUND(333.8,1)":       "334",
-		"=MROUND(333.3,2)":       "334",
-		"=MROUND(555.3,400)":     "400",
-		"=MROUND(555,1000)":      "1000",
-		"=MROUND(-555.7,-1)":     "-556",
-		"=MROUND(-555.4,-1)":     "-555",
-		"=MROUND(-1555,-1000)":   "-2000",
-		"=MROUND(MROUND(1,1),1)": "1",
-		// MULTINOMIAL
-		"=MULTINOMIAL(3,1,2,5)":        "27720",
-		`=MULTINOMIAL("",3,1,2,5)`:     "27720",
-		"=MULTINOMIAL(MULTINOMIAL(1))": "1",
-		// _xlfn.MUNIT
-		"=_xlfn.MUNIT(4)": "",
-		// ODD
-		"=ODD(22)":     "23",
-		"=ODD(1.22)":   "3",
-		"=ODD(1.22+4)": "7",
-		"=ODD(0)":      "1",
-		"=ODD(-1.3)":   "-3",
-		"=ODD(-10)":    "-11",
-		"=ODD(-3)":     "-3",
-		"=ODD(ODD(1))": "1",
-		// PI
-		"=PI()": "3.141592653589793",
-		// POWER
-		"=POWER(4,2)":          "16",
-		"=POWER(4,POWER(1,1))": "4",
-		// PRODUCT
-		"=PRODUCT(3,6)":            "18",
-		`=PRODUCT("",3,6)`:         "18",
-		`=PRODUCT(PRODUCT(1),3,6)`: "18",
-		// QUOTIENT
-		"=QUOTIENT(5,2)":             "2",
-		"=QUOTIENT(4.5,3.1)":         "1",
-		"=QUOTIENT(-10,3)":           "-3",
-		"=QUOTIENT(QUOTIENT(1,2),3)": "0",
-		// RADIANS
-		"=RADIANS(50)":           "0.872664625997165",
-		"=RADIANS(-180)":         "-3.141592653589793",
-		"=RADIANS(180)":          "3.141592653589793",
-		"=RADIANS(360)":          "6.283185307179586",
-		"=RADIANS(RADIANS(360))": "0.109662271123215",
-		// ROMAN
-		"=ROMAN(499,0)":       "CDXCIX",
-		"=ROMAN(1999,0)":      "MCMXCIX",
-		"=ROMAN(1999,1)":      "MLMVLIV",
-		"=ROMAN(1999,2)":      "MXMIX",
-		"=ROMAN(1999,3)":      "MVMIV",
-		"=ROMAN(1999,4)":      "MIM",
-		"=ROMAN(1999,-1)":     "MCMXCIX",
-		"=ROMAN(1999,5)":      "MIM",
-		"=ROMAN(1999,ODD(1))": "MLMVLIV",
-		// ROUND
-		"=ROUND(100.319,1)":       "100.30000000000001",
-		"=ROUND(5.28,1)":          "5.300000000000001",
-		"=ROUND(5.9999,3)":        "6.000000000000002",
-		"=ROUND(99.5,0)":          "100",
-		"=ROUND(-6.3,0)":          "-6",
-		"=ROUND(-100.5,0)":        "-101",
-		"=ROUND(-22.45,1)":        "-22.5",
-		"=ROUND(999,-1)":          "1000",
-		"=ROUND(991,-1)":          "990",
-		"=ROUND(ROUND(100,1),-1)": "100",
-		// ROUNDDOWN
-		"=ROUNDDOWN(99.999,1)":            "99.9",
-		"=ROUNDDOWN(99.999,2)":            "99.99000000000002",
-		"=ROUNDDOWN(99.999,0)":            "99",
-		"=ROUNDDOWN(99.999,-1)":           "90",
-		"=ROUNDDOWN(-99.999,2)":           "-99.99000000000002",
-		"=ROUNDDOWN(-99.999,-1)":          "-90",
-		"=ROUNDDOWN(ROUNDDOWN(100,1),-1)": "100",
-		// ROUNDUP`
-		"=ROUNDUP(11.111,1)":          "11.200000000000001",
-		"=ROUNDUP(11.111,2)":          "11.120000000000003",
-		"=ROUNDUP(11.111,0)":          "12",
-		"=ROUNDUP(11.111,-1)":         "20",
-		"=ROUNDUP(-11.111,2)":         "-11.120000000000003",
-		"=ROUNDUP(-11.111,-1)":        "-20",
-		"=ROUNDUP(ROUNDUP(100,1),-1)": "100",
-		// SEC
-		"=_xlfn.SEC(-3.14159265358979)": "-1",
-		"=_xlfn.SEC(0)":                 "1",
-		"=_xlfn.SEC(_xlfn.SEC(0))":      "0.54030230586814",
-		// SECH
-		"=_xlfn.SECH(-3.14159265358979)": "0.086266738334055",
-		"=_xlfn.SECH(0)":                 "1",
-		"=_xlfn.SECH(_xlfn.SECH(0))":     "0.648054273663886",
-		// SIGN
-		"=SIGN(9.5)":        "1",
-		"=SIGN(-9.5)":       "-1",
-		"=SIGN(0)":          "0",
-		"=SIGN(0.00000001)": "1",
-		"=SIGN(6-7)":        "-1",
-		"=SIGN(SIGN(-1))":   "-1",
-		// SIN
-		"=SIN(0.785398163)": "0.707106780905509",
-		"=SIN(SIN(1))":      "0.745624141665558",
-		// SINH
-		"=SINH(0)":       "0",
-		"=SINH(0.5)":     "0.521095305493747",
-		"=SINH(-2)":      "-3.626860407847019",
-		"=SINH(SINH(0))": "0",
-		// SQRT
-		"=SQRT(4)":        "2",
-		"=SQRT(SQRT(16))": "2",
-		// SQRTPI
-		"=SQRTPI(5)":         "3.963327297606011",
-		"=SQRTPI(0.2)":       "0.792665459521202",
-		"=SQRTPI(100)":       "17.72453850905516",
-		"=SQRTPI(0)":         "0",
-		"=SQRTPI(SQRTPI(0))": "0",
-		// STDEV
-		"=STDEV(F2:F9)":         "10724.978287523809",
-		"=STDEV(MUNIT(2))":      "0.577350269189626",
-		"=STDEV(0,INT(0))":      "0",
-		"=STDEV(INT(1),INT(1))": "0",
-		// STDEV.S
-		"=STDEV.S(F2:F9)": "10724.978287523809",
-		// STDEVA
-		"=STDEVA(F2:F9)":    "10724.978287523809",
-		"=STDEVA(MUNIT(2))": "0.577350269189626",
-		"=STDEVA(0,INT(0))": "0",
-		// POISSON.DIST
-		"=POISSON.DIST(20,25,FALSE)": "0.051917468608491",
-		"=POISSON.DIST(35,40,TRUE)":  "0.242414197690103",
-		// POISSON
-		"=POISSON(20,25,FALSE)": "0.051917468608491",
-		"=POISSON(35,40,TRUE)":  "0.242414197690103",
-		// SUM
-		"=SUM(1,2)":                           "3",
-		`=SUM("",1,2)`:                        "3",
-		"=SUM(1,2+3)":                         "6",
-		"=SUM(SUM(1,2),2)":                    "5",
-		"=(-2-SUM(-4+7))*5":                   "-25",
-		"SUM(1,2,3,4,5,6,7)":                  "28",
-		"=SUM(1,2)+SUM(1,2)":                  "6",
-		"=1+SUM(SUM(1,2*3),4)":                "12",
-		"=1+SUM(SUM(1,-2*3),4)":               "0",
-		"=(-2-SUM(-4*(7+7)))*5":               "270",
-		"=SUM(SUM(1+2/1)*2-3/2,2)":            "6.5",
-		"=((3+5*2)+3)/5+(-6)/4*2+3":           "3.2",
-		"=1+SUM(SUM(1,2*3),4)*-4/2+5+(4+2)*3": "2",
-		"=1+SUM(SUM(1,2*3),4)*4/3+5+(4+2)*3":  "38.666666666666664",
-		// SUMIF
-		`=SUMIF(F1:F5, "")`:             "0",
-		`=SUMIF(A1:A5, "3")`:            "3",
-		`=SUMIF(F1:F5, "=36693")`:       "36693",
-		`=SUMIF(F1:F5, "<100")`:         "0",
-		`=SUMIF(F1:F5, "<=36693")`:      "93233",
-		`=SUMIF(F1:F5, ">100")`:         "146554",
-		`=SUMIF(F1:F5, ">=100")`:        "146554",
-		`=SUMIF(F1:F5, ">=text")`:       "0",
-		`=SUMIF(F1:F5, "*Jan",F2:F5)`:   "0",
-		`=SUMIF(D3:D7,"Jan",F2:F5)`:     "112114",
-		`=SUMIF(D2:D9,"Feb",F2:F9)`:     "157559",
-		`=SUMIF(E2:E9,"North 1",F2:F9)`: "66582",
-		`=SUMIF(E2:E9,"North*",F2:F9)`:  "138772",
-		// SUMSQ
-		"=SUMSQ(A1:A4)":            "14",
-		"=SUMSQ(A1,B1,A2,B2,6)":    "82",
-		`=SUMSQ("",A1,B1,A2,B2,6)`: "82",
-		`=SUMSQ(1,SUMSQ(1))`:       "2",
-		"=SUMSQ(MUNIT(3))":         "0",
-		// TAN
-		"=TAN(1.047197551)": "1.732050806782486",
-		"=TAN(0)":           "0",
-		"=TAN(TAN(0))":      "0",
-		// TANH
-		"=TANH(0)":       "0",
-		"=TANH(0.5)":     "0.46211715726001",
-		"=TANH(-2)":      "-0.964027580075817",
-		"=TANH(TANH(0))": "0",
-		// TRUNC
-		"=TRUNC(99.999,1)":    "99.9",
-		"=TRUNC(99.999,2)":    "99.99",
-		"=TRUNC(99.999)":      "99",
-		"=TRUNC(99.999,-1)":   "90",
-		"=TRUNC(-99.999,2)":   "-99.99",
-		"=TRUNC(-99.999,-1)":  "-90",
-		"=TRUNC(TRUNC(1),-1)": "0",
-		// Statistical Functions
-		// AVERAGE
-		"=AVERAGE(INT(1))": "1",
-		"=AVERAGE(A1)":     "1",
-		"=AVERAGE(A1:A2)":  "1.5",
-		"=AVERAGE(D2:F9)":  "38014.125",
-		// AVERAGEA
-		"=AVERAGEA(INT(1))": "1",
-		"=AVERAGEA(A1)":     "1",
-		"=AVERAGEA(A1:A2)":  "1.5",
-		"=AVERAGEA(D2:F9)":  "12671.375",
-		// COUNT
-		"=COUNT()":                        "0",
-		"=COUNT(E1:F2,\"text\",1,INT(2))": "3",
-		// COUNTA
-		"=COUNTA()":                              "0",
-		"=COUNTA(A1:A5,B2:B5,\"text\",1,INT(2))": "8",
-		"=COUNTA(COUNTA(1),MUNIT(1))":            "2",
-		// COUNTBLANK
-		"=COUNTBLANK(MUNIT(1))": "0",
-		"=COUNTBLANK(1)":        "0",
-		"=COUNTBLANK(B1:C1)":    "1",
-		"=COUNTBLANK(C1)":       "1",
-		// FISHER
-		"=FISHER(-0.9)":   "-1.47221948958322",
-		"=FISHER(-0.25)":  "-0.255412811882995",
-		"=FISHER(0.8)":    "1.09861228866811",
-		"=FISHER(INT(0))": "0",
-		// FISHERINV
-		"=FISHERINV(-0.2)":   "-0.197375320224904",
-		"=FISHERINV(INT(0))": "0",
-		"=FISHERINV(2.8)":    "0.992631520201128",
-		// GAMMA
-		"=GAMMA(0.1)":    "9.513507698668732",
-		"=GAMMA(INT(1))": "1",
-		"=GAMMA(1.5)":    "0.886226925452758",
-		"=GAMMA(5.5)":    "52.34277778455352",
-		// GAMMALN
-		"=GAMMALN(4.5)":    "2.453736570842443",
-		"=GAMMALN(INT(1))": "0",
-		// HARMEAN
-		"=HARMEAN(2.5,3,0.5,1,3)":               "1.229508196721312",
-		"=HARMEAN(\"2.5\",3,0.5,1,INT(3),\"\")": "1.229508196721312",
-		// KURT
-		"=KURT(F1:F9)":           "-1.033503502551368",
-		"=KURT(F1,F2:F9)":        "-1.033503502551368",
-		"=KURT(INT(1),MUNIT(2))": "-3.333333333333336",
-		// NORM.DIST
-		"=NORM.DIST(0.8,1,0.3,TRUE)": "0.252492537546923",
-		"=NORM.DIST(50,40,20,FALSE)": "0.017603266338215",
-		// NORMDIST
-		"=NORMDIST(0.8,1,0.3,TRUE)": "0.252492537546923",
-		"=NORMDIST(50,40,20,FALSE)": "0.017603266338215",
-		// NORM.INV
-		"=NORM.INV(0.6,5,2)": "5.506694205719997",
-		// NORMINV
-		"=NORMINV(0.6,5,2)":     "5.506694205719997",
-		"=NORMINV(0.99,40,1.5)": "43.489521811582044",
-		"=NORMINV(0.02,40,1.5)": "36.91937663649545",
-		// NORM.S.DIST
-		"=NORM.S.DIST(0.8,TRUE)": "0.788144601416603",
-		// NORMSDIST
-		"=NORMSDIST(1.333333)": "0.908788725604095",
-		"=NORMSDIST(0)":        "0.5",
-		// NORM.S.INV
-		"=NORM.S.INV(0.25)": "-0.674489750223423",
-		// NORMSINV
-		"=NORMSINV(0.25)": "-0.674489750223423",
-		// LARGE
-		"=LARGE(A1:A5,1)": "3",
-		"=LARGE(A1:B5,2)": "4",
-		"=LARGE(A1,1)":    "1",
-		"=LARGE(A1:F2,1)": "36693",
-		// MAX
-		"=MAX(1)":          "1",
-		"=MAX(TRUE())":     "1",
-		"=MAX(0.5,TRUE())": "1",
-		"=MAX(FALSE())":    "0",
-		"=MAX(MUNIT(2))":   "1",
-		"=MAX(INT(1))":     "1",
-		// MAXA
-		"=MAXA(1)":          "1",
-		"=MAXA(TRUE())":     "1",
-		"=MAXA(0.5,TRUE())": "1",
-		"=MAXA(FALSE())":    "0",
-		"=MAXA(MUNIT(2))":   "1",
-		"=MAXA(INT(1))":     "1",
-		"=MAXA(A1:B4,MUNIT(1),INT(0),1,E1:F2,\"\")": "36693",
-		// MEDIAN
-		"=MEDIAN(A1:A5,12)":               "2",
-		"=MEDIAN(A1:A5)":                  "1.5",
-		"=MEDIAN(A1:A5,MEDIAN(A1:A5,12))": "2",
-		// MIN
-		"=MIN(1)":           "1",
-		"=MIN(TRUE())":      "1",
-		"=MIN(0.5,FALSE())": "0",
-		"=MIN(FALSE())":     "0",
-		"=MIN(MUNIT(2))":    "0",
-		"=MIN(INT(1))":      "1",
-		// MINA
-		"=MINA(1)":           "1",
-		"=MINA(TRUE())":      "1",
-		"=MINA(0.5,FALSE())": "0",
-		"=MINA(FALSE())":     "0",
-		"=MINA(MUNIT(2))":    "0",
-		"=MINA(INT(1))":      "1",
-		"=MINA(A1:B4,MUNIT(1),INT(0),1,E1:F2,\"\")": "0",
-		// PERCENTILE.INC
-		"=PERCENTILE.INC(A1:A4,0.2)": "0.6",
-		// PERCENTILE
-		"=PERCENTILE(A1:A4,0.2)": "0.6",
-		"=PERCENTILE(0,0)":       "0",
-		// PERMUT
-		"=PERMUT(6,6)":  "720",
-		"=PERMUT(7,6)":  "5040",
-		"=PERMUT(10,6)": "151200",
-		// PERMUTATIONA
-		"=PERMUTATIONA(6,6)": "46656",
-		"=PERMUTATIONA(7,6)": "117649",
-		// QUARTILE
-		"=QUARTILE(A1:A4,2)": "1.5",
-		// QUARTILE.INC
-		"=QUARTILE.INC(A1:A4,0)": "0",
-		// SKEW
-		"=SKEW(1,2,3,4,3)": "-0.404796008910937",
-		"=SKEW(A1:B2)":     "0",
-		"=SKEW(A1:D3)":     "0",
-		// SMALL
-		"=SMALL(A1:A5,1)": "0",
-		"=SMALL(A1:B5,2)": "1",
-		"=SMALL(A1,1)":    "1",
-		"=SMALL(A1:F2,1)": "1",
-		// VARP
-		"=VARP(A1:A5)": "1.25",
-		// VAR.P
-		"=VAR.P(A1:A5)": "1.25",
-		// Information Functions
-		// ISBLANK
-		"=ISBLANK(A1)": "FALSE",
-		"=ISBLANK(A5)": "TRUE",
-		// ISERR
-		"=ISERR(A1)":           "FALSE",
-		"=ISERR(NA())":         "FALSE",
-		"=ISERR(POWER(0,-1)))": "TRUE",
-		// ISERROR
-		"=ISERROR(A1)":   "FALSE",
-		"=ISERROR(NA())": "TRUE",
-		// ISEVEN
-		"=ISEVEN(A1)": "FALSE",
-		"=ISEVEN(A2)": "TRUE",
-		// ISNA
-		"=ISNA(A1)":   "FALSE",
-		"=ISNA(NA())": "TRUE",
-		// ISNONTEXT
-		"=ISNONTEXT(A1)":         "FALSE",
-		"=ISNONTEXT(A5)":         "TRUE",
-		`=ISNONTEXT("Excelize")`: "FALSE",
-		"=ISNONTEXT(NA())":       "TRUE",
-		// ISNUMBER
-		"=ISNUMBER(A1)": "TRUE",
-		"=ISNUMBER(D1)": "FALSE",
-		// ISODD
-		"=ISODD(A1)": "TRUE",
-		"=ISODD(A2)": "FALSE",
-		// ISTEXT
-		"=ISTEXT(D1)": "TRUE",
-		"=ISTEXT(A1)": "FALSE",
-		// N
-		"=N(10)":     "10",
-		"=N(\"10\")": "10",
-		"=N(\"x\")":  "0",
-		"=N(TRUE)":   "1",
-		"=N(FALSE)":  "0",
-		// SHEET
-		"=SHEET()": "1",
-		// T
-		"=T(\"text\")": "text",
-		"=T(N(10))":    "",
-		// Logical Functions
-		// AND
-		"=AND(0)":               "FALSE",
-		"=AND(1)":               "TRUE",
-		"=AND(1,0)":             "FALSE",
-		"=AND(0,1)":             "FALSE",
-		"=AND(1=1)":             "TRUE",
-		"=AND(1<2)":             "TRUE",
-		"=AND(1>2,2<3,2>0,3>1)": "FALSE",
-		"=AND(1=1),1=1":         "TRUE",
-		// FALSE
-		"=FALSE()": "FALSE",
-		// IFERROR
-		"=IFERROR(1/2,0)":       "0.5",
-		"=IFERROR(ISERROR(),0)": "0",
-		"=IFERROR(1/0,0)":       "0",
-		// NOT
-		"=NOT(FALSE())":     "TRUE",
-		"=NOT(\"false\")":   "TRUE",
-		"=NOT(\"true\")":    "FALSE",
-		"=NOT(ISBLANK(B1))": "TRUE",
-		// OR
-		"=OR(1)":       "TRUE",
-		"=OR(0)":       "FALSE",
-		"=OR(1=2,2=2)": "TRUE",
-		"=OR(1=2,2=3)": "FALSE",
-		// TRUE
-		"=TRUE()": "TRUE",
-		// Date and Time Functions
-		// DATE
-		"=DATE(2020,10,21)": "2020-10-21 00:00:00 +0000 UTC",
-		"=DATE(1900,1,1)":   "1899-12-31 00:00:00 +0000 UTC",
-		// DATEDIF
-		"=DATEDIF(43101,43101,\"D\")":  "0",
-		"=DATEDIF(43101,43891,\"d\")":  "790",
-		"=DATEDIF(43101,43891,\"Y\")":  "2",
-		"=DATEDIF(42156,44242,\"y\")":  "5",
-		"=DATEDIF(43101,43891,\"M\")":  "26",
-		"=DATEDIF(42171,44242,\"m\")":  "67",
-		"=DATEDIF(42156,44454,\"MD\")": "14",
-		"=DATEDIF(42171,44242,\"md\")": "30",
-		"=DATEDIF(43101,43891,\"YM\")": "2",
-		"=DATEDIF(42171,44242,\"ym\")": "7",
-		"=DATEDIF(43101,43891,\"YD\")": "59",
-		"=DATEDIF(36526,73110,\"YD\")": "60",
-		"=DATEDIF(42171,44242,\"yd\")": "244",
-		// Text Functions
-		// CHAR
-		"=CHAR(65)": "A",
-		"=CHAR(97)": "a",
-		"=CHAR(63)": "?",
-		"=CHAR(51)": "3",
-		// CLEAN
-		"=CLEAN(\"\u0009clean text\")": "clean text",
-		"=CLEAN(0)":                    "0",
-		// CODE
-		"=CODE(\"Alpha\")": "65",
-		"=CODE(\"alpha\")": "97",
-		"=CODE(\"?\")":     "63",
-		"=CODE(\"3\")":     "51",
-		"=CODE(\"\")":      "0",
-		// CONCAT
-		"=CONCAT(TRUE(),1,FALSE(),\"0\",INT(2))": "TRUE1FALSE02",
-		// CONCATENATE
-		"=CONCATENATE(TRUE(),1,FALSE(),\"0\",INT(2))": "TRUE1FALSE02",
-		// EXACT
-		"=EXACT(1,\"1\")":     "TRUE",
-		"=EXACT(1,1)":         "TRUE",
-		"=EXACT(\"A\",\"a\")": "FALSE",
-		// FIXED
-		"=FIXED(5123.591)":         "5,123.591",
-		"=FIXED(5123.591,1)":       "5,123.6",
-		"=FIXED(5123.591,0)":       "5,124",
-		"=FIXED(5123.591,-1)":      "5,120",
-		"=FIXED(5123.591,-2)":      "5,100",
-		"=FIXED(5123.591,-3,TRUE)": "5000",
-		"=FIXED(5123.591,-5)":      "0",
-		"=FIXED(-77262.23973,-5)":  "-100,000",
-		// FIND
-		"=FIND(\"T\",\"Original Text\")":   "10",
-		"=FIND(\"t\",\"Original Text\")":   "13",
-		"=FIND(\"i\",\"Original Text\")":   "3",
-		"=FIND(\"i\",\"Original Text\",4)": "5",
-		"=FIND(\"\",\"Original Text\")":    "1",
-		"=FIND(\"\",\"Original Text\",2)":  "2",
-		// FINDB
-		"=FINDB(\"T\",\"Original Text\")":   "10",
-		"=FINDB(\"t\",\"Original Text\")":   "13",
-		"=FINDB(\"i\",\"Original Text\")":   "3",
-		"=FINDB(\"i\",\"Original Text\",4)": "5",
-		"=FINDB(\"\",\"Original Text\")":    "1",
-		"=FINDB(\"\",\"Original Text\",2)":  "2",
-		// LEFT
-		"=LEFT(\"Original Text\")":    "O",
-		"=LEFT(\"Original Text\",4)":  "Orig",
-		"=LEFT(\"Original Text\",0)":  "",
-		"=LEFT(\"Original Text\",13)": "Original Text",
-		"=LEFT(\"Original Text\",20)": "Original Text",
-		// LEFTB
-		"=LEFTB(\"Original Text\")":    "O",
-		"=LEFTB(\"Original Text\",4)":  "Orig",
-		"=LEFTB(\"Original Text\",0)":  "",
-		"=LEFTB(\"Original Text\",13)": "Original Text",
-		"=LEFTB(\"Original Text\",20)": "Original Text",
-		// LEN
-		"=LEN(\"\")": "0",
-		"=LEN(D1)":   "5",
-		// LENB
-		"=LENB(\"\")": "0",
-		"=LENB(D1)":   "5",
-		// LOWER
-		"=LOWER(\"test\")":     "test",
-		"=LOWER(\"TEST\")":     "test",
-		"=LOWER(\"Test\")":     "test",
-		"=LOWER(\"TEST 123\")": "test 123",
-		// MID
-		"=MID(\"Original Text\",7,1)": "a",
-		"=MID(\"Original Text\",4,7)": "ginal T",
-		"=MID(\"255 years\",3,1)":     "5",
-		"=MID(\"text\",3,6)":          "xt",
-		"=MID(\"text\",6,0)":          "",
-		// MIDB
-		"=MIDB(\"Original Text\",7,1)": "a",
-		"=MIDB(\"Original Text\",4,7)": "ginal T",
-		"=MIDB(\"255 years\",3,1)":     "5",
-		"=MIDB(\"text\",3,6)":          "xt",
-		"=MIDB(\"text\",6,0)":          "",
-		// PROPER
-		"=PROPER(\"this is a test sentence\")": "This Is A Test Sentence",
-		"=PROPER(\"THIS IS A TEST SENTENCE\")": "This Is A Test Sentence",
-		"=PROPER(\"123tEST teXT\")":            "123Test Text",
-		"=PROPER(\"Mr. SMITH's address\")":     "Mr. Smith'S Address",
-		// REPLACE
-		"=REPLACE(\"test string\",7,3,\"X\")":          "test sXng",
-		"=REPLACE(\"second test string\",8,4,\"XXX\")": "second XXX string",
-		"=REPLACE(\"text\",5,0,\" and char\")":         "text and char",
-		"=REPLACE(\"text\",1,20,\"char and \")":        "char and ",
-		// REPLACEB
-		"=REPLACEB(\"test string\",7,3,\"X\")":          "test sXng",
-		"=REPLACEB(\"second test string\",8,4,\"XXX\")": "second XXX string",
-		"=REPLACEB(\"text\",5,0,\" and char\")":         "text and char",
-		"=REPLACEB(\"text\",1,20,\"char and \")":        "char and ",
-		// REPT
-		"=REPT(\"*\",0)":  "",
-		"=REPT(\"*\",1)":  "*",
-		"=REPT(\"**\",2)": "****",
-		// RIGHT
-		"=RIGHT(\"Original Text\")":    "t",
-		"=RIGHT(\"Original Text\",4)":  "Text",
-		"=RIGHT(\"Original Text\",0)":  "",
-		"=RIGHT(\"Original Text\",13)": "Original Text",
-		"=RIGHT(\"Original Text\",20)": "Original Text",
-		// RIGHTB
-		"=RIGHTB(\"Original Text\")":    "t",
-		"=RIGHTB(\"Original Text\",4)":  "Text",
-		"=RIGHTB(\"Original Text\",0)":  "",
-		"=RIGHTB(\"Original Text\",13)": "Original Text",
-		"=RIGHTB(\"Original Text\",20)": "Original Text",
-		// SUBSTITUTE
-		"=SUBSTITUTE(\"abab\",\"a\",\"X\")":                      "XbXb",
-		"=SUBSTITUTE(\"abab\",\"a\",\"X\",2)":                    "abXb",
-		"=SUBSTITUTE(\"abab\",\"x\",\"X\",2)":                    "abab",
-		"=SUBSTITUTE(\"John is 5 years old\",\"John\",\"Jack\")": "Jack is 5 years old",
-		"=SUBSTITUTE(\"John is 5 years old\",\"5\",\"6\")":       "John is 6 years old",
-		// TRIM
-		"=TRIM(\" trim text \")": "trim text",
-		"=TRIM(0)":               "0",
-		// UNICHAR
-		"=UNICHAR(65)": "A",
-		"=UNICHAR(97)": "a",
-		"=UNICHAR(63)": "?",
-		"=UNICHAR(51)": "3",
-		// UNICODE
-		"=UNICODE(\"Alpha\")": "65",
-		"=UNICODE(\"alpha\")": "97",
-		"=UNICODE(\"?\")":     "63",
-		"=UNICODE(\"3\")":     "51",
-		// UPPER
-		"=UPPER(\"test\")":     "TEST",
-		"=UPPER(\"TEST\")":     "TEST",
-		"=UPPER(\"Test\")":     "TEST",
-		"=UPPER(\"TEST 123\")": "TEST 123",
-		// Conditional Functions
-		// IF
-		"=IF(1=1)":                              "TRUE",
-		"=IF(1<>1)":                             "FALSE",
-		"=IF(5<0, \"negative\", \"positive\")":  "positive",
-		"=IF(-2<0, \"negative\", \"positive\")": "negative",
-		// Excel Lookup and Reference Functions
-		// CHOOSE
-		"=CHOOSE(4,\"red\",\"blue\",\"green\",\"brown\")": "brown",
-		"=CHOOSE(1,\"red\",\"blue\",\"green\",\"brown\")": "red",
-		"=SUM(CHOOSE(A2,A1,B1:B2,A1:A3,A1:A4))":           "9",
-		// COLUMN
-		"=COLUMN()":                "3",
-		"=COLUMN(Sheet1!A1)":       "1",
-		"=COLUMN(Sheet1!A1:B1:C1)": "1",
-		"=COLUMN(Sheet1!F1:G1)":    "6",
-		"=COLUMN(H1)":              "8",
-		// COLUMNS
-		"=COLUMNS(B1)":                   "1",
-		"=COLUMNS(1:1)":                  "16384",
-		"=COLUMNS(Sheet1!1:1)":           "16384",
-		"=COLUMNS(B1:E5)":                "4",
-		"=COLUMNS(Sheet1!E5:H7:B1)":      "7",
-		"=COLUMNS(E5:H7:B1:C1:Z1:C1:B1)": "25",
-		"=COLUMNS(E5:B1)":                "4",
-		"=COLUMNS(EM38:HZ81)":            "92",
-		// HLOOKUP
-		"=HLOOKUP(D2,D2:D8,1,FALSE)":          "Jan",
-		"=HLOOKUP(F3,F3:F8,3,FALSE)":          "34440",
-		"=HLOOKUP(INT(F3),F3:F8,3,FALSE)":     "34440",
-		"=HLOOKUP(MUNIT(1),MUNIT(1),1,FALSE)": "1",
-		// VLOOKUP
-		"=VLOOKUP(D2,D:D,1,FALSE)":            "Jan",
-		"=VLOOKUP(D2,D1:D10,1)":               "Jan",
-		"=VLOOKUP(D2,D1:D11,1)":               "Feb",
-		"=VLOOKUP(D2,D1:D10,1,FALSE)":         "Jan",
-		"=VLOOKUP(INT(36693),F2:F2,1,FALSE)":  "36693",
-		"=VLOOKUP(INT(F2),F3:F9,1)":           "32080",
-		"=VLOOKUP(INT(F2),F3:F9,1,TRUE)":      "32080",
-		"=VLOOKUP(MUNIT(3),MUNIT(3),1)":       "0",
-		"=VLOOKUP(A1,A3:B5,1)":                "0",
-		"=VLOOKUP(MUNIT(1),MUNIT(1),1,FALSE)": "1",
-		// LOOKUP
-		"=LOOKUP(F8,F8:F9,F8:F9)":      "32080",
-		"=LOOKUP(F8,F8:F9,D8:D9)":      "Feb",
-		"=LOOKUP(1,MUNIT(1),MUNIT(1))": "1",
-		// ROW
-		"=ROW()":                "1",
-		"=ROW(Sheet1!A1)":       "1",
-		"=ROW(Sheet1!A1:B2:C3)": "1",
-		"=ROW(Sheet1!F5:G6)":    "5",
-		"=ROW(A8)":              "8",
-		// ROWS
-		"=ROWS(B1)":                    "1",
-		"=ROWS(B:B)":                   "1048576",
-		"=ROWS(Sheet1!B:B)":            "1048576",
-		"=ROWS(B1:E5)":                 "5",
-		"=ROWS(Sheet1!E5:H7:B1)":       "7",
-		"=ROWS(E5:H8:B2:C3:Z26:C3:B2)": "25",
-		"=ROWS(E5:B1)":                 "5",
-		"=ROWS(EM38:HZ81)":             "44",
-		// Web Functions
-		// ENCODEURL
-		"=ENCODEURL(\"https://xuri.me/excelize/en/?q=Save As\")": "https%3A%2F%2Fxuri.me%2Fexcelize%2Fen%2F%3Fq%3DSave%20As",
-		// Financial Functions
-		// CUMIPMT
-		"=CUMIPMT(0.05/12,60,50000,1,12,0)":  "-2294.97753732664",
-		"=CUMIPMT(0.05/12,60,50000,13,24,0)": "-1833.1000665738893",
-		// CUMPRINC
-		"=CUMPRINC(0.05/12,60,50000,1,12,0)":  "-9027.762649079885",
-		"=CUMPRINC(0.05/12,60,50000,13,24,0)": "-9489.640119832635",
-		// DB
-		"=DB(0,1000,5,1)":       "0",
-		"=DB(10000,1000,5,1)":   "3690",
-		"=DB(10000,1000,5,2)":   "2328.39",
-		"=DB(10000,1000,5,1,6)": "1845",
-		"=DB(10000,1000,5,6,6)": "238.52712458788187",
-		// DDB
-		"=DDB(0,1000,5,1)":     "0",
-		"=DDB(10000,1000,5,1)": "4000",
-		"=DDB(10000,1000,5,2)": "2400",
-		"=DDB(10000,1000,5,3)": "1440",
-		"=DDB(10000,1000,5,4)": "864",
-		"=DDB(10000,1000,5,5)": "296",
-		// DOLLARDE
-		"=DOLLARDE(1.01,16)": "1.0625",
-		// DOLLARFR
-		"=DOLLARFR(1.0625,16)": "1.01",
-		// EFFECT
-		"=EFFECT(0.1,4)":   "0.103812890625",
-		"=EFFECT(0.025,2)": "0.02515625",
-		// FV
-		"=FV(0.05/12,60,-1000)":   "68006.08284084337",
-		"=FV(0.1/4,16,-2000,0,1)": "39729.46089416617",
-		"=FV(0,16,-2000)":         "32000",
-		// FVSCHEDULE
-		"=FVSCHEDULE(10000,A1:A5)": "240000",
-		"=FVSCHEDULE(10000,0.5)":   "15000",
-		// IPMT
-		"=IPMT(0.05/12,2,60,50000)":   "-205.26988187971995",
-		"=IPMT(0.035/4,2,8,0,5000,1)": "5.257455237829077",
-		// ISPMT
-		"=ISPMT(0.05/12,1,60,50000)": "-204.8611111111111",
-		"=ISPMT(0.05/12,2,60,50000)": "-201.38888888888886",
-		"=ISPMT(0.05/12,2,1,50000)":  "208.33333333333334",
-		// NOMINAL
-		"=NOMINAL(0.025,12)": "0.024718035238113",
-		// NPER
-		"=NPER(0.04,-6000,50000)":           "10.338035071507665",
-		"=NPER(0,-6000,50000)":              "8.333333333333334",
-		"=NPER(0.06/4,-2000,60000,30000,1)": "52.794773709274764",
-		// NPV
-		"=NPV(0.02,-5000,\"\",800)": "-4133.025759323337",
-		// PDURATION
-		"=PDURATION(0.04,10000,15000)": "10.33803507150765",
-		// PMT
-		"=PMT(0,8,0,5000,1)":       "-625",
-		"=PMT(0.035/4,8,0,5000,1)": "-600.8520271804658",
-		// PPMT
-		"=PPMT(0.05/12,2,60,50000)":   "-738.2918003208238",
-		"=PPMT(0.035/4,2,8,0,5000,1)": "-606.1094824182949",
-	}
-	for formula, expected := range mathCalc {
-		f := prepareCalcData(cellData)
-		assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
-		result, err := f.CalcCellValue("Sheet1", "C1")
-		assert.NoError(t, err, formula)
-		assert.Equal(t, expected, result, formula)
-	}
-	mathCalcError := map[string]string{
-		"=1/0": "#DIV/0!",
-		// Engineering Functions
-		// BESSELI
-		"=BESSELI()":       "BESSELI requires 2 numeric arguments",
-		"=BESSELI(\"\",0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=BESSELI(0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		// BESSELJ
-		"=BESSELJ()":       "BESSELJ requires 2 numeric arguments",
-		"=BESSELJ(\"\",0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=BESSELJ(0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		// BESSELK
-		"=BESSELK()":       "BESSELK requires 2 numeric arguments",
-		"=BESSELK(\"\",0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=BESSELK(0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=BESSELK(-1,0)":   "#NUM!",
-		"=BESSELK(1,-1)":   "#NUM!",
-		// BESSELY
-		"=BESSELY()":       "BESSELY requires 2 numeric arguments",
-		"=BESSELY(\"\",0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=BESSELY(0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=BESSELY(-1,0)":   "#NUM!",
-		"=BESSELY(1,-1)":   "#NUM!",
-		// BIN2DEC
-		"=BIN2DEC()":     "BIN2DEC requires 1 numeric argument",
-		"=BIN2DEC(\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		// BIN2HEX
-		"=BIN2HEX()":               "BIN2HEX requires at least 1 argument",
-		"=BIN2HEX(1,1,1)":          "BIN2HEX allows at most 2 arguments",
-		"=BIN2HEX(\"\",1)":         "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=BIN2HEX(1,\"\")":         "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=BIN2HEX(12345678901,10)": "#NUM!",
-		"=BIN2HEX(1,-1)":           "#NUM!",
-		"=BIN2HEX(31,1)":           "#NUM!",
-		// BIN2OCT
-		"=BIN2OCT()":                 "BIN2OCT requires at least 1 argument",
-		"=BIN2OCT(1,1,1)":            "BIN2OCT allows at most 2 arguments",
-		"=BIN2OCT(\"\",1)":           "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=BIN2OCT(1,\"\")":           "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=BIN2OCT(-12345678901 ,10)": "#NUM!",
-		"=BIN2OCT(1,-1)":             "#NUM!",
-		"=BIN2OCT(8,1)":              "#NUM!",
-		// BITAND
-		"=BITAND()":        "BITAND requires 2 numeric arguments",
-		"=BITAND(-1,2)":    "#NUM!",
-		"=BITAND(2^48,2)":  "#NUM!",
-		"=BITAND(1,-1)":    "#NUM!",
-		"=BITAND(\"\",-1)": "#NUM!",
-		"=BITAND(1,\"\")":  "#NUM!",
-		"=BITAND(1,2^48)":  "#NUM!",
-		// BITLSHIFT
-		"=BITLSHIFT()":        "BITLSHIFT requires 2 numeric arguments",
-		"=BITLSHIFT(-1,2)":    "#NUM!",
-		"=BITLSHIFT(2^48,2)":  "#NUM!",
-		"=BITLSHIFT(1,-1)":    "#NUM!",
-		"=BITLSHIFT(\"\",-1)": "#NUM!",
-		"=BITLSHIFT(1,\"\")":  "#NUM!",
-		"=BITLSHIFT(1,2^48)":  "#NUM!",
-		// BITOR
-		"=BITOR()":        "BITOR requires 2 numeric arguments",
-		"=BITOR(-1,2)":    "#NUM!",
-		"=BITOR(2^48,2)":  "#NUM!",
-		"=BITOR(1,-1)":    "#NUM!",
-		"=BITOR(\"\",-1)": "#NUM!",
-		"=BITOR(1,\"\")":  "#NUM!",
-		"=BITOR(1,2^48)":  "#NUM!",
-		// BITRSHIFT
-		"=BITRSHIFT()":        "BITRSHIFT requires 2 numeric arguments",
-		"=BITRSHIFT(-1,2)":    "#NUM!",
-		"=BITRSHIFT(2^48,2)":  "#NUM!",
-		"=BITRSHIFT(1,-1)":    "#NUM!",
-		"=BITRSHIFT(\"\",-1)": "#NUM!",
-		"=BITRSHIFT(1,\"\")":  "#NUM!",
-		"=BITRSHIFT(1,2^48)":  "#NUM!",
-		// BITXOR
-		"=BITXOR()":        "BITXOR requires 2 numeric arguments",
-		"=BITXOR(-1,2)":    "#NUM!",
-		"=BITXOR(2^48,2)":  "#NUM!",
-		"=BITXOR(1,-1)":    "#NUM!",
-		"=BITXOR(\"\",-1)": "#NUM!",
-		"=BITXOR(1,\"\")":  "#NUM!",
-		"=BITXOR(1,2^48)":  "#NUM!",
-		// COMPLEX
-		"=COMPLEX()":              "COMPLEX requires at least 2 arguments",
-		"=COMPLEX(10,-5,\"\")":    "#VALUE!",
-		"=COMPLEX(\"\",0)":        "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=COMPLEX(0,\"\")":        "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=COMPLEX(10,-5,\"i\",0)": "COMPLEX allows at most 3 arguments",
-		// DEC2BIN
-		"=DEC2BIN()":        "DEC2BIN requires at least 1 argument",
-		"=DEC2BIN(1,1,1)":   "DEC2BIN allows at most 2 arguments",
-		"=DEC2BIN(\"\",1)":  "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=DEC2BIN(1,\"\")":  "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=DEC2BIN(-513,10)": "#NUM!",
-		"=DEC2BIN(1,-1)":    "#NUM!",
-		"=DEC2BIN(2,1)":     "#NUM!",
-		// DEC2HEX
-		"=DEC2HEX()":                 "DEC2HEX requires at least 1 argument",
-		"=DEC2HEX(1,1,1)":            "DEC2HEX allows at most 2 arguments",
-		"=DEC2HEX(\"\",1)":           "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=DEC2HEX(1,\"\")":           "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=DEC2HEX(-549755813888,10)": "#NUM!",
-		"=DEC2HEX(1,-1)":             "#NUM!",
-		"=DEC2HEX(31,1)":             "#NUM!",
-		// DEC2OCT
-		"=DEC2OCT()":               "DEC2OCT requires at least 1 argument",
-		"=DEC2OCT(1,1,1)":          "DEC2OCT allows at most 2 arguments",
-		"=DEC2OCT(\"\",1)":         "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=DEC2OCT(1,\"\")":         "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=DEC2OCT(-536870912 ,10)": "#NUM!",
-		"=DEC2OCT(1,-1)":           "#NUM!",
-		"=DEC2OCT(8,1)":            "#NUM!",
-		// HEX2BIN
-		"=HEX2BIN()":        "HEX2BIN requires at least 1 argument",
-		"=HEX2BIN(1,1,1)":   "HEX2BIN allows at most 2 arguments",
-		"=HEX2BIN(\"X\",1)": "strconv.ParseInt: parsing \"X\": invalid syntax",
-		"=HEX2BIN(1,\"\")":  "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=HEX2BIN(-513,10)": "strconv.ParseInt: parsing \"-\": invalid syntax",
-		"=HEX2BIN(1,-1)":    "#NUM!",
-		"=HEX2BIN(2,1)":     "#NUM!",
-		// HEX2DEC
-		"=HEX2DEC()":      "HEX2DEC requires 1 numeric argument",
-		"=HEX2DEC(\"X\")": "strconv.ParseInt: parsing \"X\": invalid syntax",
-		// HEX2OCT
-		"=HEX2OCT()":        "HEX2OCT requires at least 1 argument",
-		"=HEX2OCT(1,1,1)":   "HEX2OCT allows at most 2 arguments",
-		"=HEX2OCT(\"X\",1)": "strconv.ParseInt: parsing \"X\": invalid syntax",
-		"=HEX2OCT(1,\"\")":  "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=HEX2OCT(-513,10)": "strconv.ParseInt: parsing \"-\": invalid syntax",
-		"=HEX2OCT(1,-1)":    "#NUM!",
-		// IMABS
-		"=IMABS()":     "IMABS requires 1 argument",
-		"=IMABS(\"\")": "strconv.ParseComplex: parsing \"\": invalid syntax",
-		// IMAGINARY
-		"=IMAGINARY()":     "IMAGINARY requires 1 argument",
-		"=IMAGINARY(\"\")": "strconv.ParseComplex: parsing \"\": invalid syntax",
-		// IMARGUMENT
-		"=IMARGUMENT()":     "IMARGUMENT requires 1 argument",
-		"=IMARGUMENT(\"\")": "strconv.ParseComplex: parsing \"\": invalid syntax",
-		// IMCONJUGATE
-		"=IMCONJUGATE()":     "IMCONJUGATE requires 1 argument",
-		"=IMCONJUGATE(\"\")": "strconv.ParseComplex: parsing \"\": invalid syntax",
-		// IMCOS
-		"=IMCOS()":     "IMCOS requires 1 argument",
-		"=IMCOS(\"\")": "strconv.ParseComplex: parsing \"\": invalid syntax",
-		// IMCOSH
-		"=IMCOSH()":     "IMCOSH requires 1 argument",
-		"=IMCOSH(\"\")": "strconv.ParseComplex: parsing \"\": invalid syntax",
-		// IMCOT
-		"=IMCOT()":     "IMCOT requires 1 argument",
-		"=IMCOT(\"\")": "strconv.ParseComplex: parsing \"\": invalid syntax",
-		// IMCSC
-		"=IMCSC()":     "IMCSC requires 1 argument",
-		"=IMCSC(\"\")": "strconv.ParseComplex: parsing \"\": invalid syntax",
-		"=IMCSC(0)":    "#NUM!",
-		// IMCSCH
-		"=IMCSCH()":     "IMCSCH requires 1 argument",
-		"=IMCSCH(\"\")": "strconv.ParseComplex: parsing \"\": invalid syntax",
-		"=IMCSCH(0)":    "#NUM!",
-		// IMDIV
-		"=IMDIV()":       "IMDIV requires 2 arguments",
-		"=IMDIV(0,\"\")": "strconv.ParseComplex: parsing \"\": invalid syntax",
-		"=IMDIV(\"\",0)": "strconv.ParseComplex: parsing \"\": invalid syntax",
-		"=IMDIV(1,0)":    "#NUM!",
-		// IMEXP
-		"=IMEXP()":     "IMEXP requires 1 argument",
-		"=IMEXP(\"\")": "strconv.ParseComplex: parsing \"\": invalid syntax",
-		// IMLN
-		"=IMLN()":     "IMLN requires 1 argument",
-		"=IMLN(\"\")": "strconv.ParseComplex: parsing \"\": invalid syntax",
-		"=IMLN(0)":    "#NUM!",
-		// IMLOG10
-		"=IMLOG10()":     "IMLOG10 requires 1 argument",
-		"=IMLOG10(\"\")": "strconv.ParseComplex: parsing \"\": invalid syntax",
-		"=IMLOG10(0)":    "#NUM!",
-		// IMLOG2
-		"=IMLOG2()":     "IMLOG2 requires 1 argument",
-		"=IMLOG2(\"\")": "strconv.ParseComplex: parsing \"\": invalid syntax",
-		"=IMLOG2(0)":    "#NUM!",
-		// IMPOWER
-		"=IMPOWER()":       "IMPOWER requires 2 arguments",
-		"=IMPOWER(0,\"\")": "strconv.ParseComplex: parsing \"\": invalid syntax",
-		"=IMPOWER(\"\",0)": "strconv.ParseComplex: parsing \"\": invalid syntax",
-		"=IMPOWER(0,0)":    "#NUM!",
-		"=IMPOWER(0,-1)":   "#NUM!",
-		// IMPRODUCT
-		"=IMPRODUCT(\"x\")": "strconv.ParseComplex: parsing \"x\": invalid syntax",
-		"=IMPRODUCT(A1:D1)": "strconv.ParseComplex: parsing \"Month\": invalid syntax",
-		// IMREAL
-		"=IMREAL()":     "IMREAL requires 1 argument",
-		"=IMREAL(\"\")": "strconv.ParseComplex: parsing \"\": invalid syntax",
-		// IMSEC
-		"=IMSEC()":     "IMSEC requires 1 argument",
-		"=IMSEC(\"\")": "strconv.ParseComplex: parsing \"\": invalid syntax",
-		// IMSECH
-		"=IMSECH()":     "IMSECH requires 1 argument",
-		"=IMSECH(\"\")": "strconv.ParseComplex: parsing \"\": invalid syntax",
-		// IMSIN
-		"=IMSIN()":     "IMSIN requires 1 argument",
-		"=IMSIN(\"\")": "strconv.ParseComplex: parsing \"\": invalid syntax",
-		// IMSINH
-		"=IMSINH()":     "IMSINH requires 1 argument",
-		"=IMSINH(\"\")": "strconv.ParseComplex: parsing \"\": invalid syntax",
-		// IMSQRT
-		"=IMSQRT()":     "IMSQRT requires 1 argument",
-		"=IMSQRT(\"\")": "strconv.ParseComplex: parsing \"\": invalid syntax",
-		// IMSUB
-		"=IMSUB()":       "IMSUB requires 2 arguments",
-		"=IMSUB(0,\"\")": "strconv.ParseComplex: parsing \"\": invalid syntax",
-		"=IMSUB(\"\",0)": "strconv.ParseComplex: parsing \"\": invalid syntax",
-		// IMSUM
-		"=IMSUM()":     "IMSUM requires at least 1 argument",
-		"=IMSUM(\"\")": "strconv.ParseComplex: parsing \"\": invalid syntax",
-		// IMTAN
-		"=IMTAN()":     "IMTAN requires 1 argument",
-		"=IMTAN(\"\")": "strconv.ParseComplex: parsing \"\": invalid syntax",
-		// OCT2BIN
-		"=OCT2BIN()":               "OCT2BIN requires at least 1 argument",
-		"=OCT2BIN(1,1,1)":          "OCT2BIN allows at most 2 arguments",
-		"=OCT2BIN(\"\",1)":         "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=OCT2BIN(1,\"\")":         "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=OCT2BIN(-536870912 ,10)": "#NUM!",
-		"=OCT2BIN(1,-1)":           "#NUM!",
-		// OCT2DEC
-		"=OCT2DEC()":     "OCT2DEC requires 1 numeric argument",
-		"=OCT2DEC(\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		// OCT2HEX
-		"=OCT2HEX()":               "OCT2HEX requires at least 1 argument",
-		"=OCT2HEX(1,1,1)":          "OCT2HEX allows at most 2 arguments",
-		"=OCT2HEX(\"\",1)":         "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=OCT2HEX(1,\"\")":         "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=OCT2HEX(-536870912 ,10)": "#NUM!",
-		"=OCT2HEX(1,-1)":           "#NUM!",
-		// Math and Trigonometric Functions
-		// ABS
-		"=ABS()":    "ABS requires 1 numeric argument",
-		`=ABS("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		"=ABS(~)":   `invalid column name "~"`,
-		// ACOS
-		"=ACOS()":        "ACOS requires 1 numeric argument",
-		`=ACOS("X")`:     "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		"=ACOS(ACOS(0))": "#NUM!",
-		// ACOSH
-		"=ACOSH()":    "ACOSH requires 1 numeric argument",
-		`=ACOSH("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		// _xlfn.ACOT
-		"=_xlfn.ACOT()":    "ACOT requires 1 numeric argument",
-		`=_xlfn.ACOT("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		// _xlfn.ACOTH
-		"=_xlfn.ACOTH()":               "ACOTH requires 1 numeric argument",
-		`=_xlfn.ACOTH("X")`:            "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		"=_xlfn.ACOTH(_xlfn.ACOTH(2))": "#NUM!",
-		// _xlfn.ARABIC
-		"=_xlfn.ARABIC()": "ARABIC requires 1 numeric argument",
-		"=_xlfn.ARABIC(\"" + strings.Repeat("I", 256) + "\")": "#VALUE!",
-		// ASIN
-		"=ASIN()":    "ASIN requires 1 numeric argument",
-		`=ASIN("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		// ASINH
-		"=ASINH()":    "ASINH requires 1 numeric argument",
-		`=ASINH("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		// ATAN
-		"=ATAN()":    "ATAN requires 1 numeric argument",
-		`=ATAN("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		// ATANH
-		"=ATANH()":    "ATANH requires 1 numeric argument",
-		`=ATANH("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		// ATAN2
-		"=ATAN2()":      "ATAN2 requires 2 numeric arguments",
-		`=ATAN2("X",0)`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		`=ATAN2(0,"X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		// BASE
-		"=BASE()":        "BASE requires at least 2 arguments",
-		"=BASE(1,2,3,4)": "BASE allows at most 3 arguments",
-		"=BASE(1,1)":     "radix must be an integer >= 2 and <= 36",
-		`=BASE("X",2)`:   "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		`=BASE(1,"X")`:   "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		`=BASE(1,2,"X")`: "strconv.Atoi: parsing \"X\": invalid syntax",
-		// CEILING
-		"=CEILING()":      "CEILING requires at least 1 argument",
-		"=CEILING(1,2,3)": "CEILING allows at most 2 arguments",
-		"=CEILING(1,-1)":  "negative sig to CEILING invalid",
-		`=CEILING("X",0)`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		`=CEILING(0,"X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		// _xlfn.CEILING.MATH
-		"=_xlfn.CEILING.MATH()":        "CEILING.MATH requires at least 1 argument",
-		"=_xlfn.CEILING.MATH(1,2,3,4)": "CEILING.MATH allows at most 3 arguments",
-		`=_xlfn.CEILING.MATH("X")`:     "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		`=_xlfn.CEILING.MATH(1,"X")`:   "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		`=_xlfn.CEILING.MATH(1,2,"X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		// _xlfn.CEILING.PRECISE
-		"=_xlfn.CEILING.PRECISE()":      "CEILING.PRECISE requires at least 1 argument",
-		"=_xlfn.CEILING.PRECISE(1,2,3)": "CEILING.PRECISE allows at most 2 arguments",
-		`=_xlfn.CEILING.PRECISE("X",2)`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		`=_xlfn.CEILING.PRECISE(1,"X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		// COMBIN
-		"=COMBIN()":       "COMBIN requires 2 argument",
-		"=COMBIN(-1,1)":   "COMBIN requires number >= number_chosen",
-		`=COMBIN("X",1)`:  "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		`=COMBIN(-1,"X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		// _xlfn.COMBINA
-		"=_xlfn.COMBINA()":       "COMBINA requires 2 argument",
-		"=_xlfn.COMBINA(-1,1)":   "COMBINA requires number > number_chosen",
-		"=_xlfn.COMBINA(-1,-1)":  "COMBIN requires number >= number_chosen",
-		`=_xlfn.COMBINA("X",1)`:  "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		`=_xlfn.COMBINA(-1,"X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		// COS
-		"=COS()":    "COS requires 1 numeric argument",
-		`=COS("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		// COSH
-		"=COSH()":    "COSH requires 1 numeric argument",
-		`=COSH("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		// _xlfn.COT
-		"=COT()":    "COT requires 1 numeric argument",
-		`=COT("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		"=COT(0)":   "#DIV/0!",
-		// _xlfn.COTH
-		"=COTH()":    "COTH requires 1 numeric argument",
-		`=COTH("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		"=COTH(0)":   "#DIV/0!",
-		// _xlfn.CSC
-		"=_xlfn.CSC()":    "CSC requires 1 numeric argument",
-		`=_xlfn.CSC("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		"=_xlfn.CSC(0)":   "#DIV/0!",
-		// _xlfn.CSCH
-		"=_xlfn.CSCH()":    "CSCH requires 1 numeric argument",
-		`=_xlfn.CSCH("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		"=_xlfn.CSCH(0)":   "#DIV/0!",
-		// _xlfn.DECIMAL
-		"=_xlfn.DECIMAL()":          "DECIMAL requires 2 numeric arguments",
-		`=_xlfn.DECIMAL("X", 2)`:    "strconv.ParseInt: parsing \"X\": invalid syntax",
-		`=_xlfn.DECIMAL(2000, "X")`: "strconv.Atoi: parsing \"X\": invalid syntax",
-		// DEGREES
-		"=DEGREES()":    "DEGREES requires 1 numeric argument",
-		`=DEGREES("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		"=DEGREES(0)":   "#DIV/0!",
-		// EVEN
-		"=EVEN()":    "EVEN requires 1 numeric argument",
-		`=EVEN("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		// EXP
-		"=EXP()":    "EXP requires 1 numeric argument",
-		`=EXP("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		// FACT
-		"=FACT()":    "FACT requires 1 numeric argument",
-		`=FACT("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		"=FACT(-1)":  "#NUM!",
-		// FACTDOUBLE
-		"=FACTDOUBLE()":    "FACTDOUBLE requires 1 numeric argument",
-		`=FACTDOUBLE("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		"=FACTDOUBLE(-1)":  "#NUM!",
-		// FLOOR
-		"=FLOOR()":       "FLOOR requires 2 numeric arguments",
-		`=FLOOR("X",-1)`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		`=FLOOR(1,"X")`:  "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		"=FLOOR(1,-1)":   "invalid arguments to FLOOR",
-		// _xlfn.FLOOR.MATH
-		"=_xlfn.FLOOR.MATH()":        "FLOOR.MATH requires at least 1 argument",
-		"=_xlfn.FLOOR.MATH(1,2,3,4)": "FLOOR.MATH allows at most 3 arguments",
-		`=_xlfn.FLOOR.MATH("X",2,3)`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		`=_xlfn.FLOOR.MATH(1,"X",3)`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		`=_xlfn.FLOOR.MATH(1,2,"X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		// _xlfn.FLOOR.PRECISE
-		"=_xlfn.FLOOR.PRECISE()":      "FLOOR.PRECISE requires at least 1 argument",
-		"=_xlfn.FLOOR.PRECISE(1,2,3)": "FLOOR.PRECISE allows at most 2 arguments",
-		`=_xlfn.FLOOR.PRECISE("X",2)`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		`=_xlfn.FLOOR.PRECISE(1,"X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		// GCD
-		"=GCD()":     "GCD requires at least 1 argument",
-		"=GCD(\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=GCD(-1)":   "GCD only accepts positive arguments",
-		"=GCD(1,-1)": "GCD only accepts positive arguments",
-		`=GCD("X")`:  "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		// INT
-		"=INT()":    "INT requires 1 numeric argument",
-		`=INT("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		// ISO.CEILING
-		"=ISO.CEILING()":      "ISO.CEILING requires at least 1 argument",
-		"=ISO.CEILING(1,2,3)": "ISO.CEILING allows at most 2 arguments",
-		`=ISO.CEILING("X",2)`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		`=ISO.CEILING(1,"X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		// LCM
-		"=LCM()":     "LCM requires at least 1 argument",
-		"=LCM(-1)":   "LCM only accepts positive arguments",
-		"=LCM(1,-1)": "LCM only accepts positive arguments",
-		`=LCM("X")`:  "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		// LN
-		"=LN()":    "LN requires 1 numeric argument",
-		`=LN("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		// LOG
-		"=LOG()":      "LOG requires at least 1 argument",
-		"=LOG(1,2,3)": "LOG allows at most 2 arguments",
-		`=LOG("X",1)`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		`=LOG(1,"X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		"=LOG(0,0)":   "#DIV/0!",
-		"=LOG(1,0)":   "#DIV/0!",
-		"=LOG(1,1)":   "#DIV/0!",
-		// LOG10
-		"=LOG10()":    "LOG10 requires 1 numeric argument",
-		`=LOG10("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		// MDETERM
-		"MDETERM()": "MDETERM requires at least 1 argument",
-		// MOD
-		"=MOD()":      "MOD requires 2 numeric arguments",
-		"=MOD(6,0)":   "MOD divide by zero",
-		`=MOD("X",0)`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		`=MOD(6,"X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		// MROUND
-		"=MROUND()":      "MROUND requires 2 numeric arguments",
-		"=MROUND(1,0)":   "#NUM!",
-		"=MROUND(1,-1)":  "#NUM!",
-		`=MROUND("X",0)`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		`=MROUND(1,"X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		// MULTINOMIAL
-		`=MULTINOMIAL("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		// _xlfn.MUNIT
-		"=_xlfn.MUNIT()":    "MUNIT requires 1 numeric argument",
-		`=_xlfn.MUNIT("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		"=_xlfn.MUNIT(-1)":  "",
-		// ODD
-		"=ODD()":    "ODD requires 1 numeric argument",
-		`=ODD("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		// PI
-		"=PI(1)": "PI accepts no arguments",
-		// POWER
-		`=POWER("X",1)`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		`=POWER(1,"X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		"=POWER(0,0)":   "#NUM!",
-		"=POWER(0,-1)":  "#DIV/0!",
-		"=POWER(1)":     "POWER requires 2 numeric arguments",
-		// PRODUCT
-		`=PRODUCT("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		// QUOTIENT
-		`=QUOTIENT("X",1)`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		`=QUOTIENT(1,"X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		"=QUOTIENT(1,0)":   "#DIV/0!",
-		"=QUOTIENT(1)":     "QUOTIENT requires 2 numeric arguments",
-		// RADIANS
-		`=RADIANS("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		"=RADIANS()":    "RADIANS requires 1 numeric argument",
-		// RAND
-		"=RAND(1)": "RAND accepts no arguments",
-		// RANDBETWEEN
-		`=RANDBETWEEN("X",1)`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		`=RANDBETWEEN(1,"X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		"=RANDBETWEEN()":      "RANDBETWEEN requires 2 numeric arguments",
-		"=RANDBETWEEN(2,1)":   "#NUM!",
-		// ROMAN
-		"=ROMAN()":      "ROMAN requires at least 1 argument",
-		"=ROMAN(1,2,3)": "ROMAN allows at most 2 arguments",
-		`=ROMAN("X")`:   "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		`=ROMAN("X",1)`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		// ROUND
-		"=ROUND()":      "ROUND requires 2 numeric arguments",
-		`=ROUND("X",1)`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		`=ROUND(1,"X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		// ROUNDDOWN
-		"=ROUNDDOWN()":      "ROUNDDOWN requires 2 numeric arguments",
-		`=ROUNDDOWN("X",1)`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		`=ROUNDDOWN(1,"X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		// ROUNDUP
-		"=ROUNDUP()":      "ROUNDUP requires 2 numeric arguments",
-		`=ROUNDUP("X",1)`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		`=ROUNDUP(1,"X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		// SEC
-		"=_xlfn.SEC()":    "SEC requires 1 numeric argument",
-		`=_xlfn.SEC("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		// _xlfn.SECH
-		"=_xlfn.SECH()":    "SECH requires 1 numeric argument",
-		`=_xlfn.SECH("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		// SIGN
-		"=SIGN()":    "SIGN requires 1 numeric argument",
-		`=SIGN("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		// SIN
-		"=SIN()":    "SIN requires 1 numeric argument",
-		`=SIN("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		// SINH
-		"=SINH()":    "SINH requires 1 numeric argument",
-		`=SINH("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		// SQRT
-		"=SQRT()":    "SQRT requires 1 numeric argument",
-		`=SQRT("")`:  "strconv.ParseFloat: parsing \"\": invalid syntax",
-		`=SQRT("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		"=SQRT(-1)":  "#NUM!",
-		// SQRTPI
-		"=SQRTPI()":    "SQRTPI requires 1 numeric argument",
-		`=SQRTPI("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		// STDEV
-		"=STDEV()":      "STDEV requires at least 1 argument",
-		"=STDEV(E2:E9)": "#DIV/0!",
-		// STDEV.S
-		"=STDEV.S()": "STDEV.S requires at least 1 argument",
-		// STDEVA
-		"=STDEVA()":      "STDEVA requires at least 1 argument",
-		"=STDEVA(E2:E9)": "#DIV/0!",
-		// POISSON.DIST
-		"=POISSON.DIST()": "POISSON.DIST requires 3 arguments",
-		// POISSON
-		"=POISSON()":             "POISSON requires 3 arguments",
-		"=POISSON(\"\",0,FALSE)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=POISSON(0,\"\",FALSE)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=POISSON(0,0,\"\")":     "strconv.ParseBool: parsing \"\": invalid syntax",
-		"=POISSON(0,-1,TRUE)":    "#N/A",
-		// SUM
-		"=SUM((":   ErrInvalidFormula.Error(),
-		"=SUM(-)":  ErrInvalidFormula.Error(),
-		"=SUM(1+)": ErrInvalidFormula.Error(),
-		"=SUM(1-)": ErrInvalidFormula.Error(),
-		"=SUM(1*)": ErrInvalidFormula.Error(),
-		"=SUM(1/)": ErrInvalidFormula.Error(),
-		// SUMIF
-		"=SUMIF()": "SUMIF requires at least 2 argument",
-		// SUMSQ
-		`=SUMSQ("X")`:   "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		"=SUMSQ(C1:D2)": "strconv.ParseFloat: parsing \"Month\": invalid syntax",
-		// TAN
-		"=TAN()":    "TAN requires 1 numeric argument",
-		`=TAN("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		// TANH
-		"=TANH()":    "TANH requires 1 numeric argument",
-		`=TANH("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		// TRUNC
-		"=TRUNC()":      "TRUNC requires at least 1 argument",
-		`=TRUNC("X")`:   "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		`=TRUNC(1,"X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
-		// Statistical Functions
-		// AVERAGE
-		"=AVERAGE(H1)": "AVERAGE divide by zero",
-		// AVERAGE
-		"=AVERAGEA(H1)": "AVERAGEA divide by zero",
-		// COUNTBLANK
-		"=COUNTBLANK()":    "COUNTBLANK requires 1 argument",
-		"=COUNTBLANK(1,2)": "COUNTBLANK requires 1 argument",
-		// FISHER
-		"=FISHER()":         "FISHER requires 1 numeric argument",
-		"=FISHER(2)":        "#N/A",
-		"=FISHER(INT(-2)))": "#N/A",
-		"=FISHER(F1)":       "FISHER requires 1 numeric argument",
-		// FISHERINV
-		"=FISHERINV()":   "FISHERINV requires 1 numeric argument",
-		"=FISHERINV(F1)": "FISHERINV requires 1 numeric argument",
-		// GAMMA
-		"=GAMMA()":       "GAMMA requires 1 numeric argument",
-		"=GAMMA(F1)":     "GAMMA requires 1 numeric argument",
-		"=GAMMA(0)":      "#N/A",
-		"=GAMMA(INT(0))": "#N/A",
-		// GAMMALN
-		"=GAMMALN()":       "GAMMALN requires 1 numeric argument",
-		"=GAMMALN(F1)":     "GAMMALN requires 1 numeric argument",
-		"=GAMMALN(0)":      "#N/A",
-		"=GAMMALN(INT(0))": "#N/A",
-		// HARMEAN
-		"=HARMEAN()":   "HARMEAN requires at least 1 argument",
-		"=HARMEAN(-1)": "#N/A",
-		"=HARMEAN(0)":  "#N/A",
-		// KURT
-		"=KURT()":          "KURT requires at least 1 argument",
-		"=KURT(F1,INT(1))": "#DIV/0!",
-		// NORM.DIST
-		"=NORM.DIST()": "NORM.DIST requires 4 arguments",
-		// NORMDIST
-		"=NORMDIST()":               "NORMDIST requires 4 arguments",
-		"=NORMDIST(\"\",0,0,FALSE)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=NORMDIST(0,\"\",0,FALSE)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=NORMDIST(0,0,\"\",FALSE)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=NORMDIST(0,0,0,\"\")":     "strconv.ParseBool: parsing \"\": invalid syntax",
-		"=NORMDIST(0,0,-1,TRUE)":    "#N/A",
-		// NORM.INV
-		"=NORM.INV()": "NORM.INV requires 3 arguments",
-		// NORMINV
-		"=NORMINV()":         "NORMINV requires 3 arguments",
-		"=NORMINV(\"\",0,0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=NORMINV(0,\"\",0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=NORMINV(0,0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=NORMINV(0,0,-1)":   "#N/A",
-		"=NORMINV(-1,0,0)":   "#N/A",
-		"=NORMINV(0,0,0)":    "#NUM!",
-		// NORM.S.DIST
-		"=NORM.S.DIST()": "NORM.S.DIST requires 2 numeric arguments",
-		// NORMSDIST
-		"=NORMSDIST()": "NORMSDIST requires 1 numeric argument",
-		// NORM.S.INV
-		"=NORM.S.INV()": "NORM.S.INV requires 1 numeric argument",
-		// NORMSINV
-		"=NORMSINV()": "NORMSINV requires 1 numeric argument",
-		// LARGE
-		"=LARGE()":           "LARGE requires 2 arguments",
-		"=LARGE(A1:A5,0)":    "k should be > 0",
-		"=LARGE(A1:A5,6)":    "k should be <= length of array",
-		"=LARGE(A1:A5,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		// MAX
-		"=MAX()":     "MAX requires at least 1 argument",
-		"=MAX(NA())": "#N/A",
-		// MAXA
-		"=MAXA()":     "MAXA requires at least 1 argument",
-		"=MAXA(NA())": "#N/A",
-		// MEDIAN
-		"=MEDIAN()":      "MEDIAN requires at least 1 argument",
-		"=MEDIAN(\"\")":  "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=MEDIAN(D1:D2)": "strconv.ParseFloat: parsing \"Month\": invalid syntax",
-		// MIN
-		"=MIN()":     "MIN requires at least 1 argument",
-		"=MIN(NA())": "#N/A",
-		// MINA
-		"=MINA()":     "MINA requires at least 1 argument",
-		"=MINA(NA())": "#N/A",
-		// PERCENTILE.INC
-		"=PERCENTILE.INC()": "PERCENTILE.INC requires 2 arguments",
-		// PERCENTILE
-		"=PERCENTILE()":       "PERCENTILE requires 2 arguments",
-		"=PERCENTILE(0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=PERCENTILE(0,-1)":   "#N/A",
-		"=PERCENTILE(NA(),1)": "#N/A",
-		// PERMUT
-		"=PERMUT()":       "PERMUT requires 2 numeric arguments",
-		"=PERMUT(\"\",0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=PERMUT(0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=PERMUT(6,8)":    "#N/A",
-		// PERMUTATIONA
-		"=PERMUTATIONA()":       "PERMUTATIONA requires 2 numeric arguments",
-		"=PERMUTATIONA(\"\",0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=PERMUTATIONA(0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=PERMUTATIONA(-1,0)":   "#N/A",
-		"=PERMUTATIONA(0,-1)":   "#N/A",
-		// QUARTILE
-		"=QUARTILE()":           "QUARTILE requires 2 arguments",
-		"=QUARTILE(A1:A4,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=QUARTILE(A1:A4,-1)":   "#NUM!",
-		"=QUARTILE(A1:A4,5)":    "#NUM!",
-		// QUARTILE.INC
-		"=QUARTILE.INC()": "QUARTILE.INC requires 2 arguments",
-		// SKEW
-		"=SKEW()":     "SKEW requires at least 1 argument",
-		"=SKEW(\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=SKEW(0)":    "#DIV/0!",
-		// SMALL
-		"=SMALL()":           "SMALL requires 2 arguments",
-		"=SMALL(A1:A5,0)":    "k should be > 0",
-		"=SMALL(A1:A5,6)":    "k should be <= length of array",
-		"=SMALL(A1:A5,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		// VARP
-		"=VARP()":     "VARP requires at least 1 argument",
-		"=VARP(\"\")": "#DIV/0!",
-		// VAR.P
-		"=VAR.P()":     "VAR.P requires at least 1 argument",
-		"=VAR.P(\"\")": "#DIV/0!",
-		// Information Functions
-		// ISBLANK
-		"=ISBLANK(A1,A2)": "ISBLANK requires 1 argument",
-		// ISERR
-		"=ISERR()": "ISERR requires 1 argument",
-		// ISERROR
-		"=ISERROR()": "ISERROR requires 1 argument",
-		// ISEVEN
-		"=ISEVEN()":       "ISEVEN requires 1 argument",
-		`=ISEVEN("text")`: "strconv.Atoi: parsing \"text\": invalid syntax",
-		// ISNA
-		"=ISNA()": "ISNA requires 1 argument",
-		// ISNONTEXT
-		"=ISNONTEXT()": "ISNONTEXT requires 1 argument",
-		// ISNUMBER
-		"=ISNUMBER()": "ISNUMBER requires 1 argument",
-		// ISODD
-		"=ISODD()":       "ISODD requires 1 argument",
-		`=ISODD("text")`: "strconv.Atoi: parsing \"text\": invalid syntax",
-		// ISTEXT
-		"=ISTEXT()": "ISTEXT requires 1 argument",
-		// N
-		"=N()":     "N requires 1 argument",
-		"=N(NA())": "#N/A",
-		// NA
-		"=NA()":  "#N/A",
-		"=NA(1)": "NA accepts no arguments",
-		// SHEET
-		"=SHEET(1)": "SHEET accepts no arguments",
-		// T
-		"=T()":     "T requires 1 argument",
-		"=T(NA())": "#N/A",
-		// Logical Functions
-		// AND
-		`=AND("text")`: "strconv.ParseFloat: parsing \"text\": invalid syntax",
-		`=AND(A1:B1)`:  "#VALUE!",
-		"=AND()":       "AND requires at least 1 argument",
-		"=AND(1" + strings.Repeat(",1", 30) + ")": "AND accepts at most 30 arguments",
-		// FALSE
-		"=FALSE(A1)": "FALSE takes no arguments",
-		// IFERROR
-		"=IFERROR()": "IFERROR requires 2 arguments",
-		// NOT
-		"=NOT()":      "NOT requires 1 argument",
-		"=NOT(NOT())": "NOT requires 1 argument",
-		"=NOT(\"\")":  "NOT expects 1 boolean or numeric argument",
-		// OR
-		`=OR("text")`:                            "strconv.ParseFloat: parsing \"text\": invalid syntax",
-		`=OR(A1:B1)`:                             "#VALUE!",
-		"=OR()":                                  "OR requires at least 1 argument",
-		"=OR(1" + strings.Repeat(",1", 30) + ")": "OR accepts at most 30 arguments",
-		// TRUE
-		"=TRUE(A1)": "TRUE takes no arguments",
-		// Date and Time Functions
-		// DATE
-		"=DATE()":               "DATE requires 3 number arguments",
-		`=DATE("text",10,21)`:   "DATE requires 3 number arguments",
-		`=DATE(2020,"text",21)`: "DATE requires 3 number arguments",
-		`=DATE(2020,10,"text")`: "DATE requires 3 number arguments",
-		// DATEDIF
-		"=DATEDIF()":                  "DATEDIF requires 3 number arguments",
-		"=DATEDIF(\"\",\"\",\"\")":    "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=DATEDIF(43891,43101,\"Y\")": "start_date > end_date",
-		"=DATEDIF(43101,43891,\"x\")": "DATEDIF has invalid unit",
-		// NOW
-		"=NOW(A1)": "NOW accepts no arguments",
-		// TODAY
-		"=TODAY(A1)": "TODAY accepts no arguments",
-		// Text Functions
-		// CHAR
-		"=CHAR()":     "CHAR requires 1 argument",
-		"=CHAR(-1)":   "#VALUE!",
-		"=CHAR(256)":  "#VALUE!",
-		"=CHAR(\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		// CLEAN
-		"=CLEAN()":    "CLEAN requires 1 argument",
-		"=CLEAN(1,2)": "CLEAN requires 1 argument",
-		// CODE
-		"=CODE()":    "CODE requires 1 argument",
-		"=CODE(1,2)": "CODE requires 1 argument",
-		// CONCAT
-		"=CONCAT(MUNIT(2))": "CONCAT requires arguments to be strings",
-		// CONCATENATE
-		"=CONCATENATE(MUNIT(2))": "CONCATENATE requires arguments to be strings",
-		// EXACT
-		"=EXACT()":      "EXACT requires 2 arguments",
-		"=EXACT(1,2,3)": "EXACT requires 2 arguments",
-		// FIXED
-		"=FIXED()":         "FIXED requires at least 1 argument",
-		"=FIXED(0,1,2,3)":  "FIXED allows at most 3 arguments",
-		"=FIXED(\"\")":     "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=FIXED(0,\"\")":   "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=FIXED(0,0,\"\")": "strconv.ParseBool: parsing \"\": invalid syntax",
-		// FIND
-		"=FIND()":                 "FIND requires at least 2 arguments",
-		"=FIND(1,2,3,4)":          "FIND allows at most 3 arguments",
-		"=FIND(\"x\",\"\")":       "#VALUE!",
-		"=FIND(\"x\",\"x\",-1)":   "#VALUE!",
-		"=FIND(\"x\",\"x\",\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		// FINDB
-		"=FINDB()":                 "FINDB requires at least 2 arguments",
-		"=FINDB(1,2,3,4)":          "FINDB allows at most 3 arguments",
-		"=FINDB(\"x\",\"\")":       "#VALUE!",
-		"=FINDB(\"x\",\"x\",-1)":   "#VALUE!",
-		"=FINDB(\"x\",\"x\",\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		// LEFT
-		"=LEFT()":          "LEFT requires at least 1 argument",
-		"=LEFT(\"\",2,3)":  "LEFT allows at most 2 arguments",
-		"=LEFT(\"\",\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=LEFT(\"\",-1)":   "#VALUE!",
-		// LEFTB
-		"=LEFTB()":          "LEFTB requires at least 1 argument",
-		"=LEFTB(\"\",2,3)":  "LEFTB allows at most 2 arguments",
-		"=LEFTB(\"\",\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=LEFTB(\"\",-1)":   "#VALUE!",
-		// LEN
-		"=LEN()": "LEN requires 1 string argument",
-		// LENB
-		"=LENB()": "LENB requires 1 string argument",
-		// LOWER
-		"=LOWER()":    "LOWER requires 1 argument",
-		"=LOWER(1,2)": "LOWER requires 1 argument",
-		// MID
-		"=MID()":            "MID requires 3 arguments",
-		"=MID(\"\",-1,1)":   "#VALUE!",
-		"=MID(\"\",\"\",1)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=MID(\"\",1,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		// MIDB
-		"=MIDB()":            "MIDB requires 3 arguments",
-		"=MIDB(\"\",-1,1)":   "#VALUE!",
-		"=MIDB(\"\",\"\",1)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=MIDB(\"\",1,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		// PROPER
-		"=PROPER()":    "PROPER requires 1 argument",
-		"=PROPER(1,2)": "PROPER requires 1 argument",
-		// REPLACE
-		"=REPLACE()":                           "REPLACE requires 4 arguments",
-		"=REPLACE(\"text\",0,4,\"string\")":    "#VALUE!",
-		"=REPLACE(\"text\",\"\",0,\"string\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=REPLACE(\"text\",1,\"\",\"string\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		// REPLACEB
-		"=REPLACEB()":                           "REPLACEB requires 4 arguments",
-		"=REPLACEB(\"text\",0,4,\"string\")":    "#VALUE!",
-		"=REPLACEB(\"text\",\"\",0,\"string\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=REPLACEB(\"text\",1,\"\",\"string\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		// REPT
-		"=REPT()":            "REPT requires 2 arguments",
-		"=REPT(INT(0),2)":    "REPT requires first argument to be a string",
-		"=REPT(\"*\",\"*\")": "REPT requires second argument to be a number",
-		"=REPT(\"*\",-1)":    "REPT requires second argument to be >= 0",
-		// RIGHT
-		"=RIGHT()":          "RIGHT requires at least 1 argument",
-		"=RIGHT(\"\",2,3)":  "RIGHT allows at most 2 arguments",
-		"=RIGHT(\"\",\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=RIGHT(\"\",-1)":   "#VALUE!",
-		// RIGHTB
-		"=RIGHTB()":          "RIGHTB requires at least 1 argument",
-		"=RIGHTB(\"\",2,3)":  "RIGHTB allows at most 2 arguments",
-		"=RIGHTB(\"\",\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=RIGHTB(\"\",-1)":   "#VALUE!",
-		// SUBSTITUTE
-		"=SUBSTITUTE()":                    "SUBSTITUTE requires 3 or 4 arguments",
-		"=SUBSTITUTE(\"\",\"\",\"\",\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=SUBSTITUTE(\"\",\"\",\"\",0)":    "instance_num should be > 0",
-		// TRIM
-		"=TRIM()":    "TRIM requires 1 argument",
-		"=TRIM(1,2)": "TRIM requires 1 argument",
-		// UNICHAR
-		"=UNICHAR()":      "UNICHAR requires 1 argument",
-		"=UNICHAR(\"\")":  "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=UNICHAR(55296)": "#VALUE!",
-		"=UNICHAR(0)":     "#VALUE!",
-		// UNICODE
-		"=UNICODE()":     "UNICODE requires 1 argument",
-		"=UNICODE(\"\")": "#VALUE!",
-		// UPPER
-		"=UPPER()":    "UPPER requires 1 argument",
-		"=UPPER(1,2)": "UPPER requires 1 argument",
-		// Conditional Functions
-		// IF
-		"=IF()":        "IF requires at least 1 argument",
-		"=IF(0,1,2,3)": "IF accepts at most 3 arguments",
-		"=IF(D1,1,2)":  "strconv.ParseBool: parsing \"Month\": invalid syntax",
-		// Excel Lookup and Reference Functions
-		// CHOOSE
-		"=CHOOSE()":                "CHOOSE requires 2 arguments",
-		"=CHOOSE(\"index_num\",0)": "CHOOSE requires first argument of type number",
-		"=CHOOSE(2,0)":             "index_num should be <= to the number of values",
-		// COLUMN
-		"=COLUMN(1,2)":          "COLUMN requires at most 1 argument",
-		"=COLUMN(\"\")":         "invalid reference",
-		"=COLUMN(Sheet1)":       "invalid column name \"Sheet1\"",
-		"=COLUMN(Sheet1!A1!B1)": "invalid column name \"Sheet1\"",
-		// COLUMNS
-		"=COLUMNS()":              "COLUMNS requires 1 argument",
-		"=COLUMNS(1)":             "invalid reference",
-		"=COLUMNS(\"\")":          "invalid reference",
-		"=COLUMNS(Sheet1)":        "invalid column name \"Sheet1\"",
-		"=COLUMNS(Sheet1!A1!B1)":  "invalid column name \"Sheet1\"",
-		"=COLUMNS(Sheet1!Sheet1)": "invalid column name \"Sheet1\"",
-		// HLOOKUP
-		"=HLOOKUP()":                     "HLOOKUP requires at least 3 arguments",
-		"=HLOOKUP(D2,D1,1,FALSE)":        "HLOOKUP requires second argument of table array",
-		"=HLOOKUP(D2,D:D,FALSE,FALSE)":   "HLOOKUP requires numeric row argument",
-		"=HLOOKUP(D2,D:D,1,FALSE,FALSE)": "HLOOKUP requires at most 4 arguments",
-		"=HLOOKUP(D2,D:D,1,2)":           "strconv.ParseBool: parsing \"2\": invalid syntax",
-		"=HLOOKUP(D2,D10:D10,1,FALSE)":   "HLOOKUP no result found",
-		"=HLOOKUP(D2,D2:D3,4,FALSE)":     "HLOOKUP has invalid row index",
-		"=HLOOKUP(D2,C:C,1,FALSE)":       "HLOOKUP no result found",
-		"=HLOOKUP(ISNUMBER(1),F3:F9,1)":  "HLOOKUP no result found",
-		"=HLOOKUP(INT(1),E2:E9,1)":       "HLOOKUP no result found",
-		"=HLOOKUP(MUNIT(2),MUNIT(3),1)":  "HLOOKUP no result found",
-		"=HLOOKUP(A1:B2,B2:B3,1)":        "HLOOKUP no result found",
-		// VLOOKUP
-		"=VLOOKUP()":                     "VLOOKUP requires at least 3 arguments",
-		"=VLOOKUP(D2,D1,1,FALSE)":        "VLOOKUP requires second argument of table array",
-		"=VLOOKUP(D2,D:D,FALSE,FALSE)":   "VLOOKUP requires numeric col argument",
-		"=VLOOKUP(D2,D:D,1,FALSE,FALSE)": "VLOOKUP requires at most 4 arguments",
-		"=VLOOKUP(D2,D:D,1,2)":           "strconv.ParseBool: parsing \"2\": invalid syntax",
-		"=VLOOKUP(D2,D10:D10,1,FALSE)":   "VLOOKUP no result found",
-		"=VLOOKUP(D2,D:D,2,FALSE)":       "VLOOKUP has invalid column index",
-		"=VLOOKUP(D2,C:C,1,FALSE)":       "VLOOKUP no result found",
-		"=VLOOKUP(ISNUMBER(1),F3:F9,1)":  "VLOOKUP no result found",
-		"=VLOOKUP(INT(1),E2:E9,1)":       "VLOOKUP no result found",
-		"=VLOOKUP(MUNIT(2),MUNIT(3),1)":  "VLOOKUP no result found",
-		"=VLOOKUP(1,G1:H2,1,FALSE)":      "VLOOKUP no result found",
-		// LOOKUP
-		"=LOOKUP()":                     "LOOKUP requires at least 2 arguments",
-		"=LOOKUP(D2,D1,D2)":             "LOOKUP requires second argument of table array",
-		"=LOOKUP(D2,D1,D2,FALSE)":       "LOOKUP requires at most 3 arguments",
-		"=LOOKUP(D1,MUNIT(1),MUNIT(1))": "LOOKUP no result found",
-		// ROW
-		"=ROW(1,2)":          "ROW requires at most 1 argument",
-		"=ROW(\"\")":         "invalid reference",
-		"=ROW(Sheet1)":       "invalid column name \"Sheet1\"",
-		"=ROW(Sheet1!A1!B1)": "invalid column name \"Sheet1\"",
-		// ROWS
-		"=ROWS()":              "ROWS requires 1 argument",
-		"=ROWS(1)":             "invalid reference",
-		"=ROWS(\"\")":          "invalid reference",
-		"=ROWS(Sheet1)":        "invalid column name \"Sheet1\"",
-		"=ROWS(Sheet1!A1!B1)":  "invalid column name \"Sheet1\"",
-		"=ROWS(Sheet1!Sheet1)": "invalid column name \"Sheet1\"",
-		// Web Functions
-		// ENCODEURL
-		"=ENCODEURL()": "ENCODEURL requires 1 argument",
-		// Financial Functions
-		// CUMIPMT
-		"=CUMIPMT()":               "CUMIPMT requires 6 arguments",
-		"=CUMIPMT(0,0,0,0,0,2)":    "#N/A",
-		"=CUMIPMT(0,0,0,-1,0,0)":   "#N/A",
-		"=CUMIPMT(0,0,0,1,0,0)":    "#N/A",
-		"=CUMIPMT(\"\",0,0,0,0,0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=CUMIPMT(0,\"\",0,0,0,0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=CUMIPMT(0,0,\"\",0,0,0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=CUMIPMT(0,0,0,\"\",0,0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=CUMIPMT(0,0,0,0,\"\",0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=CUMIPMT(0,0,0,0,0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		// CUMPRINC
-		"=CUMPRINC()":               "CUMPRINC requires 6 arguments",
-		"=CUMPRINC(0,0,0,0,0,2)":    "#N/A",
-		"=CUMPRINC(0,0,0,-1,0,0)":   "#N/A",
-		"=CUMPRINC(0,0,0,1,0,0)":    "#N/A",
-		"=CUMPRINC(\"\",0,0,0,0,0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=CUMPRINC(0,\"\",0,0,0,0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=CUMPRINC(0,0,\"\",0,0,0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=CUMPRINC(0,0,0,\"\",0,0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=CUMPRINC(0,0,0,0,\"\",0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=CUMPRINC(0,0,0,0,0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		// DB
-		"=DB()":             "DB requires at least 4 arguments",
-		"=DB(0,0,0,0,0,0)":  "DB allows at most 5 arguments",
-		"=DB(-1,0,0,0)":     "#N/A",
-		"=DB(\"\",0,0,0,0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=DB(0,\"\",0,0,0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=DB(0,0,\"\",0,0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=DB(0,0,0,\"\",0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=DB(0,0,0,0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		// DDB
-		"=DDB()":             "DDB requires at least 4 arguments",
-		"=DDB(0,0,0,0,0,0)":  "DDB allows at most 5 arguments",
-		"=DDB(-1,0,0,0)":     "#N/A",
-		"=DDB(\"\",0,0,0,0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=DDB(0,\"\",0,0,0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=DDB(0,0,\"\",0,0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=DDB(0,0,0,\"\",0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=DDB(0,0,0,0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		// DOLLARDE
-		"=DOLLARDE()":       "DOLLARDE requires 2 arguments",
-		"=DOLLARDE(\"\",0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=DOLLARDE(0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=DOLLARDE(0,-1)":   "#NUM!",
-		"=DOLLARDE(0,0)":    "#DIV/0!",
-		// DOLLARFR
-		"=DOLLARFR()":       "DOLLARFR requires 2 arguments",
-		"=DOLLARFR(\"\",0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=DOLLARFR(0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=DOLLARFR(0,-1)":   "#NUM!",
-		"=DOLLARFR(0,0)":    "#DIV/0!",
-		// EFFECT
-		"=EFFECT()":       "EFFECT requires 2 arguments",
-		"=EFFECT(\"\",0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=EFFECT(0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=EFFECT(0,0)":    "#NUM!",
-		"=EFFECT(1,0)":    "#NUM!",
-		// FV
-		"=FV()":              "FV requires at least 3 arguments",
-		"=FV(0,0,0,0,0,0,0)": "FV allows at most 5 arguments",
-		"=FV(0,0,0,0,2)":     "#N/A",
-		"=FV(\"\",0,0,0,0)":  "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=FV(0,\"\",0,0,0)":  "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=FV(0,0,\"\",0,0)":  "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=FV(0,0,0,\"\",0)":  "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=FV(0,0,0,0,\"\")":  "strconv.ParseFloat: parsing \"\": invalid syntax",
-		// FVSCHEDULE
-		"=FVSCHEDULE()":        "FVSCHEDULE requires 2 arguments",
-		"=FVSCHEDULE(\"\",0)":  "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=FVSCHEDULE(0,\"x\")": "strconv.ParseFloat: parsing \"x\": invalid syntax",
-		// IPMT
-		"=IPMT()":               "IPMT requires at least 4 arguments",
-		"=IPMT(0,0,0,0,0,0,0)":  "IPMT allows at most 6 arguments",
-		"=IPMT(0,0,0,0,0,2)":    "#N/A",
-		"=IPMT(0,-1,0,0,0,0)":   "#N/A",
-		"=IPMT(0,1,0,0,0,0)":    "#N/A",
-		"=IPMT(\"\",0,0,0,0,0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=IPMT(0,\"\",0,0,0,0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=IPMT(0,0,\"\",0,0,0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=IPMT(0,0,0,\"\",0,0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=IPMT(0,0,0,0,\"\",0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=IPMT(0,0,0,0,0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		// ISPMT
-		"=ISPMT()":           "ISPMT requires 4 arguments",
-		"=ISPMT(\"\",0,0,0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=ISPMT(0,\"\",0,0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=ISPMT(0,0,\"\",0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=ISPMT(0,0,0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		// NOMINAL
-		"=NOMINAL()":       "NOMINAL requires 2 arguments",
-		"=NOMINAL(\"\",0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=NOMINAL(0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=NOMINAL(0,0)":    "#NUM!",
-		"=NOMINAL(1,0)":    "#NUM!",
-		// NPER
-		"=NPER()":             "NPER requires at least 3 arguments",
-		"=NPER(0,0,0,0,0,0)":  "NPER allows at most 5 arguments",
-		"=NPER(0,0,0)":        "#NUM!",
-		"=NPER(0,0,0,0,2)":    "#N/A",
-		"=NPER(\"\",0,0,0,0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=NPER(0,\"\",0,0,0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=NPER(0,0,\"\",0,0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=NPER(0,0,0,\"\",0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=NPER(0,0,0,0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		// NPV
-		"=NPV()":       "NPV requires at least 2 arguments",
-		"=NPV(\"\",0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		// PDURATION
-		"=PDURATION()":         "PDURATION requires 3 arguments",
-		"=PDURATION(\"\",0,0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=PDURATION(0,\"\",0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=PDURATION(0,0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=PDURATION(0,0,0)":    "#NUM!",
-		// PMT
-		"=PMT()":             "PMT requires at least 3 arguments",
-		"=PMT(0,0,0,0,0,0)":  "PMT allows at most 5 arguments",
-		"=PMT(0,0,0,0,2)":    "#N/A",
-		"=PMT(\"\",0,0,0,0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=PMT(0,\"\",0,0,0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=PMT(0,0,\"\",0,0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=PMT(0,0,0,\"\",0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=PMT(0,0,0,0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		// PPMT
-		"=PPMT()":               "PPMT requires at least 4 arguments",
-		"=PPMT(0,0,0,0,0,0,0)":  "PPMT allows at most 6 arguments",
-		"=PPMT(0,0,0,0,0,2)":    "#N/A",
-		"=PPMT(0,-1,0,0,0,0)":   "#N/A",
-		"=PPMT(0,1,0,0,0,0)":    "#N/A",
-		"=PPMT(\"\",0,0,0,0,0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=PPMT(0,\"\",0,0,0,0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=PPMT(0,0,\"\",0,0,0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=PPMT(0,0,0,\"\",0,0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=PPMT(0,0,0,0,\"\",0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=PPMT(0,0,0,0,0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
-	}
-	for formula, expected := range mathCalcError {
-		f := prepareCalcData(cellData)
-		assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
-		result, err := f.CalcCellValue("Sheet1", "C1")
-		assert.EqualError(t, err, expected, formula)
-		assert.Equal(t, "", result, formula)
-	}
-
-	referenceCalc := map[string]string{
-		// MDETERM
-		"=MDETERM(A1:B2)": "-3",
-		// PRODUCT
-		"=PRODUCT(Sheet1!A1:Sheet1!A1:A2,A2)": "4",
-		// IMPRODUCT
-		"=IMPRODUCT(Sheet1!A1:Sheet1!A1:A2,A2)": "4",
-		// SUM
-		"=A1/A3":                          "0.333333333333333",
-		"=SUM(A1:A2)":                     "3",
-		"=SUM(Sheet1!A1,A2)":              "3",
-		"=(-2-SUM(-4+A2))*5":              "0",
-		"=SUM(Sheet1!A1:Sheet1!A1:A2,A2)": "5",
-		"=SUM(A1,A2,A3)*SUM(2,3)":         "30",
-		"=1+SUM(SUM(A1+A2/A3)*(2-3),2)":   "1.333333333333334",
-		"=A1/A2/SUM(A1:A2:B1)":            "0.041666666666667",
-		"=A1/A2/SUM(A1:A2:B1)*A3":         "0.125",
-		"=SUM(B1:D1)":                     "4",
-		"=SUM(\"X\")":                     "0",
-	}
-	for formula, expected := range referenceCalc {
-		f := prepareCalcData(cellData)
-		assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
-		result, err := f.CalcCellValue("Sheet1", "C1")
-		assert.NoError(t, err)
-		assert.Equal(t, expected, result, formula)
-	}
-
-	referenceCalcError := map[string]string{
-		// MDETERM
-		"=MDETERM(A1:B3)": "#VALUE!",
-		// SUM
-		"=1+SUM(SUM(A1+A2/A4)*(2-3),2)": "#DIV/0!",
-	}
-	for formula, expected := range referenceCalcError {
-		f := prepareCalcData(cellData)
-		assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
-		result, err := f.CalcCellValue("Sheet1", "C1")
-		assert.EqualError(t, err, expected)
-		assert.Equal(t, "", result, formula)
-	}
-
-	volatileFuncs := []string{
-		"=NOW()",
-		"=RAND()",
-		"=RANDBETWEEN(1,2)",
-		"=TODAY()",
-	}
-	for _, formula := range volatileFuncs {
-		f := prepareCalcData(cellData)
-		assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
-		_, err := f.CalcCellValue("Sheet1", "C1")
-		assert.NoError(t, err)
-	}
-
-	// Test get calculated cell value on not formula cell.
-	f := prepareCalcData(cellData)
-	result, err := f.CalcCellValue("Sheet1", "A1")
-	assert.NoError(t, err)
-	assert.Equal(t, "", result)
-	// Test get calculated cell value on not exists worksheet.
-	f = prepareCalcData(cellData)
-	_, err = f.CalcCellValue("SheetN", "A1")
-	assert.EqualError(t, err, "sheet SheetN is not exist")
-	// Test get calculated cell value with not support formula.
-	f = prepareCalcData(cellData)
-	assert.NoError(t, f.SetCellFormula("Sheet1", "A1", "=UNSUPPORT(A1)"))
-	_, err = f.CalcCellValue("Sheet1", "A1")
-	assert.EqualError(t, err, "not support UNSUPPORT function")
-	assert.NoError(t, f.SaveAs(filepath.Join("test", "TestCalcCellValue.xlsx")))
-
-}
-
-func TestCalculate(t *testing.T) {
-	err := `strconv.ParseFloat: parsing "string": invalid syntax`
-	opd := NewStack()
-	opd.Push(efp.Token{TValue: "string"})
-	opt := efp.Token{TValue: "-", TType: efp.TokenTypeOperatorPrefix}
-	assert.EqualError(t, calculate(opd, opt), err)
-	opd.Push(efp.Token{TValue: "string"})
-	opd.Push(efp.Token{TValue: "string"})
-	opt = efp.Token{TValue: "-", TType: efp.TokenTypeOperatorInfix}
-	assert.EqualError(t, calculate(opd, opt), err)
-}
-
-func TestCalcWithDefinedName(t *testing.T) {
-	cellData := [][]interface{}{
-		{"A1 value", "B1 value", nil},
-	}
-	f := prepareCalcData(cellData)
-	assert.NoError(t, f.SetDefinedName(&DefinedName{Name: "defined_name1", RefersTo: "Sheet1!A1", Scope: "Workbook"}))
-	assert.NoError(t, f.SetDefinedName(&DefinedName{Name: "defined_name1", RefersTo: "Sheet1!B1", Scope: "Sheet1"}))
-	assert.NoError(t, f.SetCellFormula("Sheet1", "C1", "=defined_name1"))
-	result, err := f.CalcCellValue("Sheet1", "C1")
-	assert.NoError(t, err)
-	// DefinedName with scope WorkSheet takes precedence over DefinedName with scope Workbook, so we should get B1 value
-	assert.Equal(t, "B1 value", result, "=defined_name1")
-}
-
-func TestCalcArithmeticOperations(t *testing.T) {
-	err := `strconv.ParseFloat: parsing "text": invalid syntax`
-	assert.EqualError(t, calcPow("1", "text", nil), err)
-	assert.EqualError(t, calcPow("text", "1", nil), err)
-	assert.EqualError(t, calcL("1", "text", nil), err)
-	assert.EqualError(t, calcL("text", "1", nil), err)
-	assert.EqualError(t, calcLe("1", "text", nil), err)
-	assert.EqualError(t, calcLe("text", "1", nil), err)
-	assert.EqualError(t, calcG("1", "text", nil), err)
-	assert.EqualError(t, calcG("text", "1", nil), err)
-	assert.EqualError(t, calcGe("1", "text", nil), err)
-	assert.EqualError(t, calcGe("text", "1", nil), err)
-	assert.EqualError(t, calcAdd("1", "text", nil), err)
-	assert.EqualError(t, calcAdd("text", "1", nil), err)
-	assert.EqualError(t, calcAdd("1", "text", nil), err)
-	assert.EqualError(t, calcAdd("text", "1", nil), err)
-	assert.EqualError(t, calcSubtract("1", "text", nil), err)
-	assert.EqualError(t, calcSubtract("text", "1", nil), err)
-	assert.EqualError(t, calcMultiply("1", "text", nil), err)
-	assert.EqualError(t, calcMultiply("text", "1", nil), err)
-	assert.EqualError(t, calcDiv("1", "text", nil), err)
-	assert.EqualError(t, calcDiv("text", "1", nil), err)
-}
-
-func TestCalcISBLANK(t *testing.T) {
-	argsList := list.New()
-	argsList.PushBack(formulaArg{
-		Type: ArgUnknown,
-	})
-	fn := formulaFuncs{}
-	result := fn.ISBLANK(argsList)
-	assert.Equal(t, result.String, "TRUE")
-	assert.Empty(t, result.Error)
-}
-
-func TestCalcAND(t *testing.T) {
-	argsList := list.New()
-	argsList.PushBack(formulaArg{
-		Type: ArgUnknown,
-	})
-	fn := formulaFuncs{}
-	result := fn.AND(argsList)
-	assert.Equal(t, result.String, "")
-	assert.Empty(t, result.Error)
-}
-
-func TestCalcOR(t *testing.T) {
-	argsList := list.New()
-	argsList.PushBack(formulaArg{
-		Type: ArgUnknown,
-	})
-	fn := formulaFuncs{}
-	result := fn.OR(argsList)
-	assert.Equal(t, result.String, "FALSE")
-	assert.Empty(t, result.Error)
-}
-
-func TestCalcDet(t *testing.T) {
-	assert.Equal(t, det([][]float64{
-		{1, 2, 3, 4},
-		{2, 3, 4, 5},
-		{3, 4, 5, 6},
-		{4, 5, 6, 7},
-	}), float64(0))
-}
-
-func TestCalcToBool(t *testing.T) {
-	b := newBoolFormulaArg(true).ToBool()
-	assert.Equal(t, b.Boolean, true)
-	assert.Equal(t, b.Number, 1.0)
-}
-
-func TestCalcToList(t *testing.T) {
-	assert.Equal(t, []formulaArg(nil), newEmptyFormulaArg().ToList())
-	formulaList := []formulaArg{newEmptyFormulaArg()}
-	assert.Equal(t, formulaList, newListFormulaArg(formulaList).ToList())
-}
-
-func TestCalcCompareFormulaArg(t *testing.T) {
-	assert.Equal(t, compareFormulaArg(newEmptyFormulaArg(), newEmptyFormulaArg(), false, false), criteriaEq)
-	lhs := newListFormulaArg([]formulaArg{newEmptyFormulaArg()})
-	rhs := newListFormulaArg([]formulaArg{newEmptyFormulaArg(), newEmptyFormulaArg()})
-	assert.Equal(t, compareFormulaArg(lhs, rhs, false, false), criteriaL)
-	assert.Equal(t, compareFormulaArg(rhs, lhs, false, false), criteriaG)
-
-	lhs = newListFormulaArg([]formulaArg{newBoolFormulaArg(true)})
-	rhs = newListFormulaArg([]formulaArg{newBoolFormulaArg(true)})
-	assert.Equal(t, compareFormulaArg(lhs, rhs, false, false), criteriaEq)
-
-	assert.Equal(t, compareFormulaArg(formulaArg{Type: ArgUnknown}, formulaArg{Type: ArgUnknown}, false, false), criteriaErr)
-}
-
-func TestCalcMatchPattern(t *testing.T) {
-	assert.True(t, matchPattern("", ""))
-	assert.True(t, matchPattern("file/*", "file/abc/bcd/def"))
-	assert.True(t, matchPattern("*", ""))
-	assert.False(t, matchPattern("file/?", "file/abc/bcd/def"))
-}
-
-func TestCalcVLOOKUP(t *testing.T) {
-	cellData := [][]interface{}{
-		{nil, nil, nil, nil, nil, nil},
-		{nil, "Score", "Grade", nil, nil, nil},
-		{nil, 0, "F", nil, "Score", 85},
-		{nil, 60, "D", nil, "Grade"},
-		{nil, 70, "C", nil, nil, nil},
-		{nil, 80, "b", nil, nil, nil},
-		{nil, 90, "A", nil, nil, nil},
-		{nil, 85, "B", nil, nil, nil},
-		{nil, nil, nil, nil, nil, nil},
-	}
-	f := prepareCalcData(cellData)
-	calc := map[string]string{
-		"=VLOOKUP(F3,B3:C8,2)":       "b",
-		"=VLOOKUP(F3,B3:C8,2,TRUE)":  "b",
-		"=VLOOKUP(F3,B3:C8,2,FALSE)": "B",
-	}
-	for formula, expected := range calc {
-		assert.NoError(t, f.SetCellFormula("Sheet1", "F4", formula))
-		result, err := f.CalcCellValue("Sheet1", "F4")
-		assert.NoError(t, err, formula)
-		assert.Equal(t, expected, result, formula)
-	}
-	calcError := map[string]string{
-		"=VLOOKUP(INT(1),C3:C3,1,FALSE)": "VLOOKUP no result found",
-	}
-	for formula, expected := range calcError {
-		assert.NoError(t, f.SetCellFormula("Sheet1", "F4", formula))
-		result, err := f.CalcCellValue("Sheet1", "F4")
-		assert.EqualError(t, err, expected, formula)
-		assert.Equal(t, "", result, formula)
-	}
-}
-
-func TestCalcBoolean(t *testing.T) {
-	cellData := [][]interface{}{
-		{0.5, "TRUE", -0.5, "FALSE"},
-	}
-	f := prepareCalcData(cellData)
-	formulaList := map[string]string{
-		"=AVERAGEA(A1:C1)":  "0.333333333333333",
-		"=MAX(0.5,B1)":      "0.5",
-		"=MAX(A1:B1)":       "0.5",
-		"=MAXA(A1:B1)":      "1",
-		"=MAXA(0.5,B1)":     "1",
-		"=MIN(-0.5,D1)":     "-0.5",
-		"=MIN(C1:D1)":       "-0.5",
-		"=MINA(C1:D1)":      "-0.5",
-		"=MINA(-0.5,D1)":    "-0.5",
-		"=STDEV(A1:C1)":     "0.707106781186548",
-		"=STDEV(A1,B1,C1)":  "0.707106781186548",
-		"=STDEVA(A1:C1,B1)": "0.707106781186548",
-	}
-	for formula, expected := range formulaList {
-		assert.NoError(t, f.SetCellFormula("Sheet1", "B10", formula))
-		result, err := f.CalcCellValue("Sheet1", "B10")
-		assert.NoError(t, err, formula)
-		assert.Equal(t, expected, result, formula)
-	}
-}
-
-func TestCalcHLOOKUP(t *testing.T) {
-	cellData := [][]interface{}{
-		{"Example Result Table"},
-		{nil, "A", "B", "C", "E", "F"},
-		{"Math", .58, .9, .67, .76, .8},
-		{"French", .61, .71, .59, .59, .76},
-		{"Physics", .75, .45, .39, .52, .69},
-		{"Biology", .39, .55, .77, .61, .45},
-		{},
-		{"Individual Student Score"},
-		{"Student:", "Biology Score:"},
-		{"E"},
-	}
-	f := prepareCalcData(cellData)
-	formulaList := map[string]string{
-		"=HLOOKUP(A10,A2:F6,5,FALSE)":  "0.61",
-		"=HLOOKUP(D3,D3:D3,1,TRUE)":    "0.67",
-		"=HLOOKUP(F3,D3:F3,1,TRUE)":    "0.8",
-		"=HLOOKUP(A5,A2:F2,1,TRUE)":    "F",
-		"=HLOOKUP(\"D\",A2:F2,1,TRUE)": "C",
-	}
-	for formula, expected := range formulaList {
-		assert.NoError(t, f.SetCellFormula("Sheet1", "B10", formula))
-		result, err := f.CalcCellValue("Sheet1", "B10")
-		assert.NoError(t, err, formula)
-		assert.Equal(t, expected, result, formula)
-	}
-	calcError := map[string]string{
-		"=HLOOKUP(INT(1),A3:A3,1,FALSE)": "HLOOKUP no result found",
-	}
-	for formula, expected := range calcError {
-		assert.NoError(t, f.SetCellFormula("Sheet1", "B10", formula))
-		result, err := f.CalcCellValue("Sheet1", "B10")
-		assert.EqualError(t, err, expected, formula)
-		assert.Equal(t, "", result, formula)
-	}
-}
-
-func TestCalcIRR(t *testing.T) {
-	cellData := [][]interface{}{{-1}, {0.2}, {0.24}, {0.288}, {0.3456}, {0.4147}}
-	f := prepareCalcData(cellData)
-	formulaList := map[string]string{
-		"=IRR(A1:A4)":      "-0.136189509034157",
-		"=IRR(A1:A6)":      "0.130575760006905",
-		"=IRR(A1:A4,-0.1)": "-0.136189514994621",
-	}
-	for formula, expected := range formulaList {
-		assert.NoError(t, f.SetCellFormula("Sheet1", "B1", formula))
-		result, err := f.CalcCellValue("Sheet1", "B1")
-		assert.NoError(t, err, formula)
-		assert.Equal(t, expected, result, formula)
-	}
-	calcError := map[string]string{
-		"=IRR()":       "IRR requires at least 1 argument",
-		"=IRR(0,0,0)":  "IRR allows at most 2 arguments",
-		"=IRR(0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=IRR(A2:A3)":  "#NUM!",
-	}
-	for formula, expected := range calcError {
-		assert.NoError(t, f.SetCellFormula("Sheet1", "B1", formula))
-		result, err := f.CalcCellValue("Sheet1", "B1")
-		assert.EqualError(t, err, expected, formula)
-		assert.Equal(t, "", result, formula)
-	}
-}
-
-func TestCalcMIRR(t *testing.T) {
-	cellData := [][]interface{}{{-100}, {18}, {22.5}, {28}, {35.5}, {45}}
-	f := prepareCalcData(cellData)
-	formulaList := map[string]string{
-		"=MIRR(A1:A5,0.055,0.05)": "0.025376365108071",
-		"=MIRR(A1:A6,0.055,0.05)": "0.1000268752662",
-	}
-	for formula, expected := range formulaList {
-		assert.NoError(t, f.SetCellFormula("Sheet1", "B1", formula))
-		result, err := f.CalcCellValue("Sheet1", "B1")
-		assert.NoError(t, err, formula)
-		assert.Equal(t, expected, result, formula)
-	}
-	calcError := map[string]string{
-		"=MIRR()":             "MIRR requires 3 arguments",
-		"=MIRR(A1:A5,\"\",0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=MIRR(A1:A5,0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
-		"=MIRR(B1:B5,0,0)":    "#DIV/0!",
-	}
-	for formula, expected := range calcError {
-		assert.NoError(t, f.SetCellFormula("Sheet1", "B1", formula))
-		result, err := f.CalcCellValue("Sheet1", "B1")
-		assert.EqualError(t, err, expected, formula)
-		assert.Equal(t, "", result, formula)
-	}
-}

+ 4 - 6
calcchain.go

@@ -1,13 +1,11 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
+// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
 // this source code is governed by a BSD-style license that can be found in
 // the LICENSE file.
 //
 // Package excelize providing a set of functions that allow you to write to
-// and read from XLSX / XLSM / XLTM files. Supports reading and writing
-// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
-// complex components by high compatibility, and provided streaming API for
-// generating or reading data from a worksheet with huge amounts of data. This
-// library needs Go version 1.15 or later.
+// and read from XLSX files. Support reads and writes XLSX file generated by
+// Microsoft Excel™ 2007 and later. Support save file without losing original
+// charts of XLSX. This library needs Go version 1.10 or later.
 
 package excelize
 

+ 88 - 216
cell.go

@@ -1,13 +1,11 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
+// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
 // this source code is governed by a BSD-style license that can be found in
 // the LICENSE file.
 //
 // Package excelize providing a set of functions that allow you to write to
-// and read from XLSX / XLSM / XLTM files. Supports reading and writing
-// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
-// complex components by high compatibility, and provided streaming API for
-// generating or reading data from a worksheet with huge amounts of data. This
-// library needs Go version 1.15 or later.
+// and read from XLSX files. Support reads and writes XLSX file generated by
+// Microsoft Excel™ 2007 and later. Support save file without losing original
+// charts of XLSX. This library needs Go version 1.10 or later.
 
 package excelize
 
@@ -33,20 +31,22 @@ const (
 )
 
 // GetCellValue provides a function to get formatted value from cell by given
-// worksheet name and axis in spreadsheet file. If it is possible to apply a
-// format to the cell value, it will do so, if not then an error will be
-// returned, along with the raw value of the cell.
+// worksheet name and axis in XLSX file. If it is possible to apply a format
+// to the cell value, it will do so, if not then an error will be returned,
+// along with the raw value of the cell.
 func (f *File) GetCellValue(sheet, axis string) (string, error) {
 	return f.getCellStringFunc(sheet, axis, func(x *xlsxWorksheet, c *xlsxC) (string, bool, error) {
 		val, err := c.getValueFrom(f, f.sharedStringsReader())
+		if err != nil {
+			return val, false, err
+		}
 		return val, true, err
 	})
 }
 
-// SetCellValue provides a function to set the value of a cell. The specified
-// coordinates should not be in the first row of the table, a complex number
-// can be set with string text. The following shows the supported data
-// types:
+// SetCellValue provides a function to set value of a cell. The specified
+// coordinates should not be in the first row of the table. The following
+// shows the supported data types:
 //
 //    int
 //    int8
@@ -94,7 +94,7 @@ func (f *File) SetCellValue(sheet, axis string, value interface{}) error {
 	case bool:
 		err = f.SetCellBool(sheet, axis, v)
 	case nil:
-		err = f.SetCellDefault(sheet, axis, "")
+		err = f.SetCellStr(sheet, axis, "")
 	default:
 		err = f.SetCellStr(sheet, axis, fmt.Sprint(value))
 	}
@@ -132,15 +132,15 @@ func (f *File) setCellIntFunc(sheet, axis string, value interface{}) error {
 // setCellTimeFunc provides a method to process time type of value for
 // SetCellValue.
 func (f *File) setCellTimeFunc(sheet, axis string, value time.Time) error {
-	ws, err := f.workSheetReader(sheet)
+	xlsx, err := f.workSheetReader(sheet)
 	if err != nil {
 		return err
 	}
-	cellData, col, _, err := f.prepareCell(ws, sheet, axis)
+	cellData, col, _, err := f.prepareCell(xlsx, sheet, axis)
 	if err != nil {
 		return err
 	}
-	cellData.S = f.prepareCellStyle(ws, col, cellData.S)
+	cellData.S = f.prepareCellStyle(xlsx, col, cellData.S)
 
 	var isNum bool
 	cellData.T, cellData.V, isNum, err = setCellTime(value)
@@ -179,15 +179,15 @@ func setCellDuration(value time.Duration) (t string, v string) {
 // SetCellInt provides a function to set int type value of a cell by given
 // worksheet name, cell coordinates and cell value.
 func (f *File) SetCellInt(sheet, axis string, value int) error {
-	ws, err := f.workSheetReader(sheet)
+	xlsx, err := f.workSheetReader(sheet)
 	if err != nil {
 		return err
 	}
-	cellData, col, _, err := f.prepareCell(ws, sheet, axis)
+	cellData, col, _, err := f.prepareCell(xlsx, sheet, axis)
 	if err != nil {
 		return err
 	}
-	cellData.S = f.prepareCellStyle(ws, col, cellData.S)
+	cellData.S = f.prepareCellStyle(xlsx, col, cellData.S)
 	cellData.T, cellData.V = setCellInt(value)
 	return err
 }
@@ -200,15 +200,15 @@ func setCellInt(value int) (t string, v string) {
 // SetCellBool provides a function to set bool type value of a cell by given
 // worksheet name, cell name and cell value.
 func (f *File) SetCellBool(sheet, axis string, value bool) error {
-	ws, err := f.workSheetReader(sheet)
+	xlsx, err := f.workSheetReader(sheet)
 	if err != nil {
 		return err
 	}
-	cellData, col, _, err := f.prepareCell(ws, sheet, axis)
+	cellData, col, _, err := f.prepareCell(xlsx, sheet, axis)
 	if err != nil {
 		return err
 	}
-	cellData.S = f.prepareCellStyle(ws, col, cellData.S)
+	cellData.S = f.prepareCellStyle(xlsx, col, cellData.S)
 	cellData.T, cellData.V = setCellBool(value)
 	return err
 }
@@ -233,15 +233,15 @@ func setCellBool(value bool) (t string, v string) {
 //    f.SetCellFloat("Sheet1", "A1", float64(x), 2, 32)
 //
 func (f *File) SetCellFloat(sheet, axis string, value float64, prec, bitSize int) error {
-	ws, err := f.workSheetReader(sheet)
+	xlsx, err := f.workSheetReader(sheet)
 	if err != nil {
 		return err
 	}
-	cellData, col, _, err := f.prepareCell(ws, sheet, axis)
+	cellData, col, _, err := f.prepareCell(xlsx, sheet, axis)
 	if err != nil {
 		return err
 	}
-	cellData.S = f.prepareCellStyle(ws, col, cellData.S)
+	cellData.S = f.prepareCellStyle(xlsx, col, cellData.S)
 	cellData.T, cellData.V = setCellFloat(value, prec, bitSize)
 	return err
 }
@@ -254,58 +254,22 @@ func setCellFloat(value float64, prec, bitSize int) (t string, v string) {
 // SetCellStr provides a function to set string type value of a cell. Total
 // number of characters that a cell can contain 32767 characters.
 func (f *File) SetCellStr(sheet, axis, value string) error {
-	ws, err := f.workSheetReader(sheet)
+	xlsx, err := f.workSheetReader(sheet)
 	if err != nil {
 		return err
 	}
-	cellData, col, _, err := f.prepareCell(ws, sheet, axis)
+	cellData, col, _, err := f.prepareCell(xlsx, sheet, axis)
 	if err != nil {
 		return err
 	}
-	cellData.S = f.prepareCellStyle(ws, col, cellData.S)
-	cellData.T, cellData.V = f.setCellString(value)
+	cellData.S = f.prepareCellStyle(xlsx, col, cellData.S)
+	cellData.T, cellData.V, cellData.XMLSpace = setCellStr(value)
 	return err
 }
 
-// setCellString provides a function to set string type to shared string
-// table.
-func (f *File) setCellString(value string) (t string, v string) {
-	if len(value) > TotalCellChars {
-		value = value[0:TotalCellChars]
-	}
-	t = "s"
-	v = strconv.Itoa(f.setSharedString(value))
-	return
-}
-
-// setSharedString provides a function to add string to the share string table.
-func (f *File) setSharedString(val string) int {
-	sst := f.sharedStringsReader()
-	f.Lock()
-	defer f.Unlock()
-	if i, ok := f.sharedStringsMap[val]; ok {
-		return i
-	}
-	sst.Count++
-	sst.UniqueCount++
-	t := xlsxT{Val: val}
-	// Leading and ending space(s) character detection.
-	if len(val) > 0 && (val[0] == 32 || val[len(val)-1] == 32) {
-		ns := xml.Attr{
-			Name:  xml.Name{Space: NameSpaceXML, Local: "space"},
-			Value: "preserve",
-		}
-		t.Space = ns
-	}
-	sst.SI = append(sst.SI, xlsxSI{T: &t})
-	f.sharedStringsMap[val] = sst.UniqueCount - 1
-	return sst.UniqueCount - 1
-}
-
-// setCellStr provides a function to set string type to cell.
 func setCellStr(value string) (t string, v string, ns xml.Attr) {
-	if len(value) > TotalCellChars {
-		value = value[0:TotalCellChars]
+	if len(value) > 32767 {
+		value = value[0:32767]
 	}
 	// Leading and ending space(s) character detection.
 	if len(value) > 0 && (value[0] == 32 || value[len(value)-1] == 32) {
@@ -322,15 +286,15 @@ func setCellStr(value string) (t string, v string, ns xml.Attr) {
 // SetCellDefault provides a function to set string type value of a cell as
 // default format without escaping the cell.
 func (f *File) SetCellDefault(sheet, axis, value string) error {
-	ws, err := f.workSheetReader(sheet)
+	xlsx, err := f.workSheetReader(sheet)
 	if err != nil {
 		return err
 	}
-	cellData, col, _, err := f.prepareCell(ws, sheet, axis)
+	cellData, col, _, err := f.prepareCell(xlsx, sheet, axis)
 	if err != nil {
 		return err
 	}
-	cellData.S = f.prepareCellStyle(ws, col, cellData.S)
+	cellData.S = f.prepareCellStyle(xlsx, col, cellData.S)
 	cellData.T, cellData.V = setCellDefault(value)
 	return err
 }
@@ -363,17 +327,17 @@ type FormulaOpts struct {
 // SetCellFormula provides a function to set cell formula by given string and
 // worksheet name.
 func (f *File) SetCellFormula(sheet, axis, formula string, opts ...FormulaOpts) error {
-	ws, err := f.workSheetReader(sheet)
+	xlsx, err := f.workSheetReader(sheet)
 	if err != nil {
 		return err
 	}
-	cellData, _, _, err := f.prepareCell(ws, sheet, axis)
+	cellData, _, _, err := f.prepareCell(xlsx, sheet, axis)
 	if err != nil {
 		return err
 	}
 	if formula == "" {
 		cellData.F = nil
-		f.deleteCalcChain(f.getSheetID(sheet), axis)
+		f.deleteCalcChain(f.GetSheetIndex(sheet), axis)
 		return err
 	}
 
@@ -410,16 +374,16 @@ func (f *File) GetCellHyperLink(sheet, axis string) (bool, string, error) {
 		return false, "", err
 	}
 
-	ws, err := f.workSheetReader(sheet)
+	xlsx, err := f.workSheetReader(sheet)
 	if err != nil {
 		return false, "", err
 	}
-	axis, err = f.mergeCellsParser(ws, axis)
+	axis, err = f.mergeCellsParser(xlsx, axis)
 	if err != nil {
 		return false, "", err
 	}
-	if ws.Hyperlinks != nil {
-		for _, link := range ws.Hyperlinks.Hyperlink {
+	if xlsx.Hyperlinks != nil {
+		for _, link := range xlsx.Hyperlinks.Hyperlink {
 			if link.Ref == axis {
 				if link.RID != "" {
 					return true, f.getSheetRelationshipsTargetByID(sheet, link.RID), err
@@ -431,13 +395,6 @@ func (f *File) GetCellHyperLink(sheet, axis string) (bool, string, error) {
 	return false, "", err
 }
 
-// HyperlinkOpts can be passed to SetCellHyperlink to set optional hyperlink
-// attributes (e.g. display value)
-type HyperlinkOpts struct {
-	Display *string
-	Tooltip *string
-}
-
 // SetCellHyperLink provides a function to set cell hyperlink by given
 // worksheet name and link URL address. LinkType defines two types of
 // hyperlink "External" for web site or "Location" for moving to one of cell
@@ -453,29 +410,29 @@ type HyperlinkOpts struct {
 //
 //    err := f.SetCellHyperLink("Sheet1", "A3", "Sheet1!A40", "Location")
 //
-func (f *File) SetCellHyperLink(sheet, axis, link, linkType string, opts ...HyperlinkOpts) error {
+func (f *File) SetCellHyperLink(sheet, axis, link, linkType string) error {
 	// Check for correct cell name
 	if _, _, err := SplitCellName(axis); err != nil {
 		return err
 	}
 
-	ws, err := f.workSheetReader(sheet)
+	xlsx, err := f.workSheetReader(sheet)
 	if err != nil {
 		return err
 	}
-	axis, err = f.mergeCellsParser(ws, axis)
+	axis, err = f.mergeCellsParser(xlsx, axis)
 	if err != nil {
 		return err
 	}
 
 	var linkData xlsxHyperlink
 
-	if ws.Hyperlinks == nil {
-		ws.Hyperlinks = new(xlsxHyperlinks)
+	if xlsx.Hyperlinks == nil {
+		xlsx.Hyperlinks = new(xlsxHyperlinks)
 	}
 
-	if len(ws.Hyperlinks.Hyperlink) > TotalSheetHyperlinks {
-		return ErrTotalSheetHyperlinks
+	if len(xlsx.Hyperlinks.Hyperlink) > 65529 {
+		return errors.New("over maximum limit hyperlinks in a worksheet")
 	}
 
 	switch linkType {
@@ -487,7 +444,6 @@ func (f *File) SetCellHyperLink(sheet, axis, link, linkType string, opts ...Hype
 		sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetPath, "xl/worksheets/") + ".rels"
 		rID := f.addRels(sheetRels, SourceRelationshipHyperLink, link, linkType)
 		linkData.RID = "rId" + strconv.Itoa(rID)
-		f.addSheetNameSpace(sheet, SourceRelationship)
 	case "Location":
 		linkData = xlsxHyperlink{
 			Ref:      axis,
@@ -497,70 +453,10 @@ func (f *File) SetCellHyperLink(sheet, axis, link, linkType string, opts ...Hype
 		return fmt.Errorf("invalid link type %q", linkType)
 	}
 
-	for _, o := range opts {
-		if o.Display != nil {
-			linkData.Display = *o.Display
-		}
-		if o.Tooltip != nil {
-			linkData.Tooltip = *o.Tooltip
-		}
-	}
-
-	ws.Hyperlinks.Hyperlink = append(ws.Hyperlinks.Hyperlink, linkData)
+	xlsx.Hyperlinks.Hyperlink = append(xlsx.Hyperlinks.Hyperlink, linkData)
 	return nil
 }
 
-// GetCellRichText provides a function to get rich text of cell by given
-// worksheet.
-func (f *File) GetCellRichText(sheet, cell string) (runs []RichTextRun, err error) {
-	ws, err := f.workSheetReader(sheet)
-	if err != nil {
-		return
-	}
-	cellData, _, _, err := f.prepareCell(ws, sheet, cell)
-	if err != nil {
-		return
-	}
-	siIdx, err := strconv.Atoi(cellData.V)
-	if nil != err {
-		return
-	}
-	sst := f.sharedStringsReader()
-	if len(sst.SI) <= siIdx || siIdx < 0 {
-		return
-	}
-	si := sst.SI[siIdx]
-	for _, v := range si.R {
-		run := RichTextRun{
-			Text: v.T.Val,
-		}
-		if nil != v.RPr {
-			font := Font{Underline: "none"}
-			font.Bold = v.RPr.B != nil
-			font.Italic = v.RPr.I != nil
-			if v.RPr.U != nil {
-				font.Underline = "single"
-				if v.RPr.U.Val != nil {
-					font.Underline = *v.RPr.U.Val
-				}
-			}
-			if v.RPr.RFont != nil && v.RPr.RFont.Val != nil {
-				font.Family = *v.RPr.RFont.Val
-			}
-			if v.RPr.Sz != nil && v.RPr.Sz.Val != nil {
-				font.Size = *v.RPr.Sz.Val
-			}
-			font.Strike = v.RPr.Strike != nil
-			if nil != v.RPr.Color {
-				font.Color = strings.TrimPrefix(v.RPr.Color.RGB, "FF")
-			}
-			run.Font = &font
-		}
-		runs = append(runs, run)
-	}
-	return
-}
-
 // SetCellRichText provides a function to set cell with rich text by given
 // worksheet. For example, set rich text on the A1 cell of the worksheet named
 // Sheet1:
@@ -570,7 +466,7 @@ func (f *File) GetCellRichText(sheet, cell string) (runs []RichTextRun, err erro
 //    import (
 //        "fmt"
 //
-//        "github.com/360EntSecGroup-Skylar/excelize/v2"
+//        "github.com/360EntSecGroup-Skylar/excelize"
 //    )
 //
 //    func main() {
@@ -585,7 +481,7 @@ func (f *File) GetCellRichText(sheet, cell string) (runs []RichTextRun, err erro
 //        }
 //        if err := f.SetCellRichText("Sheet1", "A1", []excelize.RichTextRun{
 //            {
-//                Text: "bold",
+//                Text: "blod",
 //                Font: &excelize.Font{
 //                    Bold:   true,
 //                    Color:  "2354e8",
@@ -681,20 +577,19 @@ func (f *File) SetCellRichText(sheet, cell string, runs []RichTextRun) error {
 	for _, textRun := range runs {
 		run := xlsxR{T: &xlsxT{Val: textRun.Text}}
 		if strings.ContainsAny(textRun.Text, "\r\n ") {
-			run.T.Space = xml.Attr{Name: xml.Name{Space: NameSpaceXML, Local: "space"}, Value: "preserve"}
+			run.T.Space = "preserve"
 		}
 		fnt := textRun.Font
 		if fnt != nil {
 			rpr := xlsxRPr{}
-			trueVal := ""
 			if fnt.Bold {
-				rpr.B = &trueVal
+				rpr.B = " "
 			}
 			if fnt.Italic {
-				rpr.I = &trueVal
+				rpr.I = " "
 			}
 			if fnt.Strike {
-				rpr.Strike = &trueVal
+				rpr.Strike = " "
 			}
 			if fnt.Underline != "" {
 				rpr.U = &attrValString{Val: &fnt.Underline}
@@ -713,16 +608,19 @@ func (f *File) SetCellRichText(sheet, cell string, runs []RichTextRun) error {
 		textRuns = append(textRuns, run)
 	}
 	si.R = textRuns
-	for idx, strItem := range sst.SI {
-		if reflect.DeepEqual(strItem, si) {
-			cellData.T, cellData.V = "s", strconv.Itoa(idx)
-			return err
-		}
-	}
 	sst.SI = append(sst.SI, si)
 	sst.Count++
 	sst.UniqueCount++
 	cellData.T, cellData.V = "s", strconv.Itoa(len(sst.SI)-1)
+	f.addContentTypePart(0, "sharedStrings")
+	rels := f.relsReader("xl/_rels/workbook.xml.rels")
+	for _, rel := range rels.Relationships {
+		if rel.Target == "sharedStrings.xml" {
+			return err
+		}
+	}
+	// Update xl/_rels/workbook.xml.rels
+	f.addRels("xl/_rels/workbook.xml.rels", SourceRelationshipSharedStrings, "sharedStrings.xml", "")
 	return err
 }
 
@@ -760,11 +658,9 @@ func (f *File) SetSheetRow(sheet, axis string, slice interface{}) error {
 }
 
 // getCellInfo does common preparation for all SetCell* methods.
-func (f *File) prepareCell(ws *xlsxWorksheet, sheet, cell string) (*xlsxC, int, int, error) {
-	ws.Lock()
-	defer ws.Unlock()
+func (f *File) prepareCell(xlsx *xlsxWorksheet, sheet, cell string) (*xlsxC, int, int, error) {
 	var err error
-	cell, err = f.mergeCellsParser(ws, cell)
+	cell, err = f.mergeCellsParser(xlsx, cell)
 	if err != nil {
 		return nil, 0, 0, err
 	}
@@ -773,19 +669,19 @@ func (f *File) prepareCell(ws *xlsxWorksheet, sheet, cell string) (*xlsxC, int,
 		return nil, 0, 0, err
 	}
 
-	prepareSheetXML(ws, col, row)
+	prepareSheetXML(xlsx, col, row)
 
-	return &ws.SheetData.Row[row-1].C[col-1], col, row, err
+	return &xlsx.SheetData.Row[row-1].C[col-1], col, row, err
 }
 
 // getCellStringFunc does common value extraction workflow for all GetCell*
 // methods. Passed function implements specific part of required logic.
 func (f *File) getCellStringFunc(sheet, axis string, fn func(x *xlsxWorksheet, c *xlsxC) (string, bool, error)) (string, error) {
-	ws, err := f.workSheetReader(sheet)
+	xlsx, err := f.workSheetReader(sheet)
 	if err != nil {
 		return "", err
 	}
-	axis, err = f.mergeCellsParser(ws, axis)
+	axis, err = f.mergeCellsParser(xlsx, axis)
 	if err != nil {
 		return "", err
 	}
@@ -794,12 +690,9 @@ func (f *File) getCellStringFunc(sheet, axis string, fn func(x *xlsxWorksheet, c
 		return "", err
 	}
 
-	ws.Lock()
-	defer ws.Unlock()
-
 	lastRowNum := 0
-	if l := len(ws.SheetData.Row); l > 0 {
-		lastRowNum = ws.SheetData.Row[l-1].R
+	if l := len(xlsx.SheetData.Row); l > 0 {
+		lastRowNum = xlsx.SheetData.Row[l-1].R
 	}
 
 	// keep in mind: row starts from 1
@@ -807,8 +700,8 @@ func (f *File) getCellStringFunc(sheet, axis string, fn func(x *xlsxWorksheet, c
 		return "", nil
 	}
 
-	for rowIdx := range ws.SheetData.Row {
-		rowData := &ws.SheetData.Row[rowIdx]
+	for rowIdx := range xlsx.SheetData.Row {
+		rowData := &xlsx.SheetData.Row[rowIdx]
 		if rowData.R != row {
 			continue
 		}
@@ -817,7 +710,7 @@ func (f *File) getCellStringFunc(sheet, axis string, fn func(x *xlsxWorksheet, c
 			if axis != colData.R {
 				continue
 			}
-			val, ok, err := fn(ws, colData)
+			val, ok, err := fn(xlsx, colData)
 			if err != nil {
 				return "", err
 			}
@@ -837,39 +730,18 @@ func (f *File) formattedValue(s int, v string) string {
 		return v
 	}
 	styleSheet := f.stylesReader()
-
-	if s >= len(styleSheet.CellXfs.Xf) {
-		return v
-	}
-	var numFmtID int
-	if styleSheet.CellXfs.Xf[s].NumFmtID != nil {
-		numFmtID = *styleSheet.CellXfs.Xf[s].NumFmtID
-	}
-
-	ok := builtInNumFmtFunc[numFmtID]
+	ok := builtInNumFmtFunc[styleSheet.CellXfs.Xf[s].NumFmtID]
 	if ok != nil {
-		return ok(v, builtInNumFmt[numFmtID])
-	}
-	if styleSheet == nil || styleSheet.NumFmts == nil {
-		return v
-	}
-	for _, xlsxFmt := range styleSheet.NumFmts.NumFmt {
-		if xlsxFmt.NumFmtID == numFmtID {
-			format := strings.ToLower(xlsxFmt.FormatCode)
-			if strings.Contains(format, "y") || strings.Contains(format, "m") || strings.Contains(strings.Replace(format, "red", "", -1), "d") || strings.Contains(format, "h") {
-				return parseTime(v, format)
-			}
-			return v
-		}
+		return ok(styleSheet.CellXfs.Xf[s].NumFmtID, v)
 	}
 	return v
 }
 
 // prepareCellStyle provides a function to prepare style index of cell in
 // worksheet by given column index and style index.
-func (f *File) prepareCellStyle(ws *xlsxWorksheet, col, style int) int {
-	if ws.Cols != nil && style == 0 {
-		for _, c := range ws.Cols.Col {
+func (f *File) prepareCellStyle(xlsx *xlsxWorksheet, col, style int) int {
+	if xlsx.Cols != nil && style == 0 {
+		for _, c := range xlsx.Cols.Col {
 			if c.Min <= col && col <= c.Max {
 				style = c.Style
 			}
@@ -880,16 +752,16 @@ func (f *File) prepareCellStyle(ws *xlsxWorksheet, col, style int) int {
 
 // mergeCellsParser provides a function to check merged cells in worksheet by
 // given axis.
-func (f *File) mergeCellsParser(ws *xlsxWorksheet, axis string) (string, error) {
+func (f *File) mergeCellsParser(xlsx *xlsxWorksheet, axis string) (string, error) {
 	axis = strings.ToUpper(axis)
-	if ws.MergeCells != nil {
-		for i := 0; i < len(ws.MergeCells.Cells); i++ {
-			ok, err := f.checkCellInArea(axis, ws.MergeCells.Cells[i].Ref)
+	if xlsx.MergeCells != nil {
+		for i := 0; i < len(xlsx.MergeCells.Cells); i++ {
+			ok, err := f.checkCellInArea(axis, xlsx.MergeCells.Cells[i].Ref)
 			if err != nil {
 				return axis, err
 			}
 			if ok {
-				axis = strings.Split(ws.MergeCells.Cells[i].Ref, ":")[0]
+				axis = strings.Split(xlsx.MergeCells.Cells[i].Ref, ":")[0]
 			}
 		}
 	}
@@ -942,8 +814,8 @@ func isOverlap(rect1, rect2 []int) bool {
 //
 // Note that this function not validate ref tag to check the cell if or not in
 // allow area, and always return origin shared formula.
-func getSharedForumula(ws *xlsxWorksheet, si string) string {
-	for _, r := range ws.SheetData.Row {
+func getSharedForumula(xlsx *xlsxWorksheet, si string) string {
+	for _, r := range xlsx.SheetData.Row {
 		for _, c := range r.C {
 			if c.F != nil && c.F.Ref != "" && c.F.T == STCellFormulaTypeShared && c.F.Si == si {
 				return c.F.Content

+ 1 - 175
cell_test.go

@@ -3,37 +3,13 @@ package excelize
 import (
 	"fmt"
 	"path/filepath"
-	"reflect"
 	"strconv"
-	"strings"
-	"sync"
 	"testing"
 	"time"
 
 	"github.com/stretchr/testify/assert"
 )
 
-func TestConcurrency(t *testing.T) {
-	f := NewFile()
-	wg := new(sync.WaitGroup)
-	for i := 1; i <= 5; i++ {
-		wg.Add(1)
-		go func(val int, t *testing.T) {
-			assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("A%d", val), val))
-			assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("B%d", val), strconv.Itoa(val)))
-			_, err := f.GetCellValue("Sheet1", fmt.Sprintf("A%d", val))
-			assert.NoError(t, err)
-			wg.Done()
-		}(i, t)
-	}
-	wg.Wait()
-	val, err := f.GetCellValue("Sheet1", "A1")
-	if err != nil {
-		t.Error(err)
-	}
-	assert.Equal(t, "1", val)
-}
-
 func TestCheckCellInArea(t *testing.T) {
 	f := NewFile()
 	expectedTrueCellInAreaList := [][2]string{
@@ -114,68 +90,11 @@ func TestSetCellValue(t *testing.T) {
 	assert.EqualError(t, f.SetCellValue("Sheet1", "A", time.Duration(1e13)), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
 }
 
-func TestSetCellValues(t *testing.T) {
-	f := NewFile()
-	err := f.SetCellValue("Sheet1", "A1", time.Date(2010, time.December, 31, 0, 0, 0, 0, time.UTC))
-	assert.NoError(t, err)
-
-	v, err := f.GetCellValue("Sheet1", "A1")
-	assert.NoError(t, err)
-	assert.Equal(t, v, "12/31/10 00:00")
-
-	// test date value lower than min date supported by Excel
-	err = f.SetCellValue("Sheet1", "A1", time.Date(1600, time.December, 31, 0, 0, 0, 0, time.UTC))
-	assert.NoError(t, err)
-
-	v, err = f.GetCellValue("Sheet1", "A1")
-	assert.NoError(t, err)
-	assert.Equal(t, v, "1600-12-31T00:00:00Z")
-}
-
 func TestSetCellBool(t *testing.T) {
 	f := NewFile()
 	assert.EqualError(t, f.SetCellBool("Sheet1", "A", true), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
 }
 
-func TestGetCellValue(t *testing.T) {
-	// Test get cell value without r attribute of the row.
-	f := NewFile()
-	sheetData := `<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetData>%s</sheetData></worksheet>`
-	delete(f.Sheet, "xl/worksheets/sheet1.xml")
-	f.XLSX["xl/worksheets/sheet1.xml"] = []byte(fmt.Sprintf(sheetData, `<row r="3"><c t="str"><v>A3</v></c></row><row><c t="str"><v>A4</v></c><c t="str"><v>B4</v></c></row><row r="7"><c t="str"><v>A7</v></c><c t="str"><v>B7</v></c></row><row><c t="str"><v>A8</v></c><c t="str"><v>B8</v></c></row>`))
-	f.checked = nil
-	cells := []string{"A3", "A4", "B4", "A7", "B7"}
-	rows, err := f.GetRows("Sheet1")
-	assert.Equal(t, [][]string{nil, nil, {"A3"}, {"A4", "B4"}, nil, nil, {"A7", "B7"}, {"A8", "B8"}}, rows)
-	assert.NoError(t, err)
-	for _, cell := range cells {
-		value, err := f.GetCellValue("Sheet1", cell)
-		assert.Equal(t, cell, value)
-		assert.NoError(t, err)
-	}
-	cols, err := f.GetCols("Sheet1")
-	assert.Equal(t, [][]string{{"", "", "A3", "A4", "", "", "A7", "A8"}, {"", "", "", "B4", "", "", "B7", "B8"}}, cols)
-	assert.NoError(t, err)
-	delete(f.Sheet, "xl/worksheets/sheet1.xml")
-	f.XLSX["xl/worksheets/sheet1.xml"] = []byte(fmt.Sprintf(sheetData, `<row r="2"><c r="A2" t="str"><v>A2</v></c></row><row r="2"><c r="B2" t="str"><v>B2</v></c></row>`))
-	f.checked = nil
-	cell, err := f.GetCellValue("Sheet1", "A2")
-	assert.Equal(t, "A2", cell)
-	assert.NoError(t, err)
-	delete(f.Sheet, "xl/worksheets/sheet1.xml")
-	f.XLSX["xl/worksheets/sheet1.xml"] = []byte(fmt.Sprintf(sheetData, `<row r="2"><c r="A2" t="str"><v>A2</v></c></row><row r="2"><c r="B2" t="str"><v>B2</v></c></row>`))
-	f.checked = nil
-	rows, err = f.GetRows("Sheet1")
-	assert.Equal(t, [][]string{nil, {"A2", "B2"}}, rows)
-	assert.NoError(t, err)
-	delete(f.Sheet, "xl/worksheets/sheet1.xml")
-	f.XLSX["xl/worksheets/sheet1.xml"] = []byte(fmt.Sprintf(sheetData, `<row r="1"><c r="A1" t="str"><v>A1</v></c></row><row r="1"><c r="B1" t="str"><v>B1</v></c></row>`))
-	f.checked = nil
-	rows, err = f.GetRows("Sheet1")
-	assert.Equal(t, [][]string{{"A1", "B1"}}, rows)
-	assert.NoError(t, err)
-}
-
 func TestGetCellFormula(t *testing.T) {
 	// Test get cell formula on not exist worksheet.
 	f := NewFile()
@@ -223,66 +142,14 @@ func TestOverflowNumericCell(t *testing.T) {
 	// GOARCH=amd64 - all ok; GOARCH=386 - actual: "-2147483648"
 	assert.Equal(t, "8595602512225", val, "A1 should be 8595602512225")
 }
-func TestGetCellRichText(t *testing.T) {
-	f := NewFile()
-
-	runsSource := []RichTextRun{
-		{
-			Text: "a\n",
-		},
-		{
-			Text: "b",
-			Font: &Font{
-				Underline: "single",
-				Color:     "ff0000",
-				Bold:      true,
-				Italic:    true,
-				Family:    "Times New Roman",
-				Size:      100,
-				Strike:    true,
-			},
-		},
-	}
-	assert.NoError(t, f.SetCellRichText("Sheet1", "A1", runsSource))
-
-	runs, err := f.GetCellRichText("Sheet1", "A1")
-	assert.NoError(t, err)
 
-	assert.Equal(t, runsSource[0].Text, runs[0].Text)
-	assert.Nil(t, runs[0].Font)
-	assert.NotNil(t, runs[1].Font)
-
-	runsSource[1].Font.Color = strings.ToUpper(runsSource[1].Font.Color)
-	assert.True(t, reflect.DeepEqual(runsSource[1].Font, runs[1].Font), "should get the same font")
-
-	// Test get cell rich text when string item index overflow
-	f.Sheet["xl/worksheets/sheet1.xml"].SheetData.Row[0].C[0].V = "2"
-	runs, err = f.GetCellRichText("Sheet1", "A1")
-	assert.NoError(t, err)
-	assert.Equal(t, 0, len(runs))
-	// Test get cell rich text when string item index is negative
-	f.Sheet["xl/worksheets/sheet1.xml"].SheetData.Row[0].C[0].V = "-1"
-	runs, err = f.GetCellRichText("Sheet1", "A1")
-	assert.NoError(t, err)
-	assert.Equal(t, 0, len(runs))
-	// Test get cell rich text on invalid string item index
-	f.Sheet["xl/worksheets/sheet1.xml"].SheetData.Row[0].C[0].V = "x"
-	_, err = f.GetCellRichText("Sheet1", "A1")
-	assert.EqualError(t, err, "strconv.Atoi: parsing \"x\": invalid syntax")
-	// Test set cell rich text on not exists worksheet
-	_, err = f.GetCellRichText("SheetN", "A1")
-	assert.EqualError(t, err, "sheet SheetN is not exist")
-	// Test set cell rich text with illegal cell coordinates
-	_, err = f.GetCellRichText("Sheet1", "A")
-	assert.EqualError(t, err, `cannot convert cell "A" to coordinates: invalid cell name "A"`)
-}
 func TestSetCellRichText(t *testing.T) {
 	f := NewFile()
 	assert.NoError(t, f.SetRowHeight("Sheet1", 1, 35))
 	assert.NoError(t, f.SetColWidth("Sheet1", "A", "A", 44))
 	richTextRun := []RichTextRun{
 		{
-			Text: "bold",
+			Text: "blod",
 			Font: &Font{
 				Bold:   true,
 				Color:  "2354e8",
@@ -356,44 +223,3 @@ func TestSetCellRichText(t *testing.T) {
 	// Test set cell rich text with illegal cell coordinates
 	assert.EqualError(t, f.SetCellRichText("Sheet1", "A", richTextRun), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
 }
-
-func TestFormattedValue2(t *testing.T) {
-	f := NewFile()
-	v := f.formattedValue(0, "43528")
-	assert.Equal(t, "43528", v)
-
-	v = f.formattedValue(15, "43528")
-	assert.Equal(t, "43528", v)
-
-	v = f.formattedValue(1, "43528")
-	assert.Equal(t, "43528", v)
-	customNumFmt := "[$-409]MM/DD/YYYY"
-	_, err := f.NewStyle(&Style{
-		CustomNumFmt: &customNumFmt,
-	})
-	assert.NoError(t, err)
-	v = f.formattedValue(1, "43528")
-	assert.Equal(t, "03/04/2019", v)
-
-	// formatted value with no built-in number format ID
-	numFmtID := 5
-	f.Styles.CellXfs.Xf = append(f.Styles.CellXfs.Xf, xlsxXf{
-		NumFmtID: &numFmtID,
-	})
-	v = f.formattedValue(2, "43528")
-	assert.Equal(t, "43528", v)
-
-	// formatted value with invalid number format ID
-	f.Styles.CellXfs.Xf = append(f.Styles.CellXfs.Xf, xlsxXf{
-		NumFmtID: nil,
-	})
-	_ = f.formattedValue(3, "43528")
-
-	// formatted value with empty number format
-	f.Styles.NumFmts = nil
-	f.Styles.CellXfs.Xf = append(f.Styles.CellXfs.Xf, xlsxXf{
-		NumFmtID: &numFmtID,
-	})
-	v = f.formattedValue(1, "43528")
-	assert.Equal(t, "43528", v)
-}

+ 26 - 183
chart.go

@@ -1,13 +1,11 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
+// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
 // this source code is governed by a BSD-style license that can be found in
 // the LICENSE file.
 //
 // Package excelize providing a set of functions that allow you to write to
-// and read from XLSX / XLSM / XLTM files. Supports reading and writing
-// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
-// complex components by high compatibility, and provided streaming API for
-// generating or reading data from a worksheet with huge amounts of data. This
-// library needs Go version 1.15 or later.
+// and read from XLSX files. Support reads and writes XLSX file generated by
+// Microsoft Excel™ 2007 and later. Support save file without losing original
+// charts of XLSX. This library needs Go version 1.10 or later.
 
 package excelize
 
@@ -494,7 +492,6 @@ func parseFormatChartSet(formatSet string) (*formatChart, error) {
 		Title: formatChartTitle{
 			Name: " ",
 		},
-		VaryColors:   true,
 		ShowBlanksAs: "gap",
 	}
 	err := json.Unmarshal([]byte(formatSet), &format)
@@ -511,15 +508,12 @@ func parseFormatChartSet(formatSet string) (*formatChart, error) {
 //    import (
 //        "fmt"
 //
-//        "github.com/360EntSecGroup-Skylar/excelize/v2"
+//        "github.com/360EntSecGroup-Skylar/excelize"
 //    )
 //
 //    func main() {
-//        categories := map[string]string{
-//            "A2": "Small", "A3": "Normal", "A4": "Large",
-//            "B1": "Apple", "C1": "Orange", "D1": "Pear"}
-//        values := map[string]int{
-//            "B2": 2, "C2": 3, "D2": 3, "B3": 5, "C3": 2, "D3": 4, "B4": 6, "C4": 7, "D4": 8}
+//        categories := map[string]string{"A2": "Small", "A3": "Normal", "A4": "Large", "B1": "Apple", "C1": "Orange", "D1": "Pear"}
+//        values := map[string]int{"B2": 2, "C2": 3, "D2": 3, "B3": 5, "C3": 2, "D3": 4, "B4": 6, "C4": 7, "D4": 8}
 //        f := excelize.NewFile()
 //        for k, v := range categories {
 //            f.SetCellValue("Sheet1", k, v)
@@ -527,58 +521,11 @@ func parseFormatChartSet(formatSet string) (*formatChart, error) {
 //        for k, v := range values {
 //            f.SetCellValue("Sheet1", k, v)
 //        }
-//        if err := f.AddChart("Sheet1", "E1", `{
-//            "type": "col3DClustered",
-//            "series": [
-//            {
-//                "name": "Sheet1!$A$2",
-//                "categories": "Sheet1!$B$1:$D$1",
-//                "values": "Sheet1!$B$2:$D$2"
-//            },
-//            {
-//                "name": "Sheet1!$A$3",
-//                "categories": "Sheet1!$B$1:$D$1",
-//                "values": "Sheet1!$B$3:$D$3"
-//            },
-//            {
-//                "name": "Sheet1!$A$4",
-//                "categories": "Sheet1!$B$1:$D$1",
-//                "values": "Sheet1!$B$4:$D$4"
-//            }],
-//            "title":
-//            {
-//                "name": "Fruit 3D Clustered Column Chart"
-//            },
-//            "legend":
-//            {
-//                "none": false,
-//                "position": "bottom",
-//                "show_legend_key": false
-//            },
-//            "plotarea":
-//            {
-//                "show_bubble_size": true,
-//                "show_cat_name": false,
-//                "show_leader_lines": false,
-//                "show_percent": true,
-//                "show_series_name": true,
-//                "show_val": true
-//            },
-//            "show_blanks_as": "zero",
-//            "x_axis":
-//            {
-//                "reverse_order": true
-//            },
-//            "y_axis":
-//            {
-//                "maximum": 7.5,
-//                "minimum": 0.5
-//            }
-//        }`); err != nil {
+//        if err := f.AddChart("Sheet1", "E1", `{"type":"col3DClustered","series":[{"name":"Sheet1!$A$2","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$2:$D$2"},{"name":"Sheet1!$A$3","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$3:$D$3"},{"name":"Sheet1!$A$4","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$4:$D$4"}],"title":{"name":"Fruit 3D Clustered Column Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero","x_axis":{"reverse_order":true},"y_axis":{"maximum":7.5,"minimum":0.5}}`); err != nil {
 //            fmt.Println(err)
 //            return
 //        }
-//        // Save spreadsheet by the given path.
+//        // Save xlsx file by the given path.
 //        if err := f.SaveAs("Book1.xlsx"); err != nil {
 //            fmt.Println(err)
 //        }
@@ -651,7 +598,6 @@ func parseFormatChartSet(formatSet string) (*formatChart, error) {
 //    categories
 //    values
 //    line
-//    marker
 //
 // name: Set the name for the series. The name is displayed in the chart legend and in the formula bar. The name property is optional and if it isn't supplied it will default to Series 1..n. The name can also be a formula such as Sheet1!$A$1
 //
@@ -661,30 +607,12 @@ func parseFormatChartSet(formatSet string) (*formatChart, error) {
 //
 // line: This sets the line format of the line chart. The line property is optional and if it isn't supplied it will default style. The options that can be set is width. The range of width is 0.25pt - 999pt. If the value of width is outside the range, the default width of the line is 2pt.
 //
-// marker: This sets the marker of the line chart and scatter chart. The range of optional field 'size' is 2-72 (default value is 5). The enumeration value of optional field 'symbol' are (default value is 'auto'):
-//
-//    circle
-//    dash
-//    diamond
-//    dot
-//    none
-//    picture
-//    plus
-//    square
-//    star
-//    triangle
-//    x
-//    auto
-//
 // Set properties of the chart legend. The options that can be set are:
 //
-//    none
 //    position
 //    show_legend_key
 //
-// none: Specified if show the legend without overlapping the chart. The default value is 'false'.
-//
-// position: Set the position of the chart legend. The default legend position is right. This parameter only takes effect when 'none' is false. The available positions are:
+// position: Set the position of the chart legend. The default legend position is right. The available positions are:
 //
 //    top
 //    bottom
@@ -708,12 +636,10 @@ func parseFormatChartSet(formatSet string) (*formatChart, error) {
 //
 // gap: Specifies that blank values shall be left as a gap.
 //
-// span: Specifies that blank values shall be spanned with a line.
+// sapn: Specifies that blank values shall be spanned with a line.
 //
 // zero: Specifies that blank values shall be treated as zero.
 //
-// Specifies that each data marker in the series has a different color by vary_colors. The default value is true.
-//
 // Set chart offset, scale, aspect ratio setting and print settings by format, same as function AddPicture.
 //
 // Set the position of the chart plot area by plotarea. The properties that can be set are:
@@ -780,15 +706,12 @@ func parseFormatChartSet(formatSet string) (*formatChart, error) {
 //    import (
 //        "fmt"
 //
-//        "github.com/360EntSecGroup-Skylar/excelize/v2"
+//        "github.com/360EntSecGroup-Skylar/excelize"
 //    )
 //
 //    func main() {
-//        categories := map[string]string{
-//            "A2": "Small", "A3": "Normal", "A4": "Large",
-//            "B1": "Apple", "C1": "Orange", "D1": "Pear"}
-//        values := map[string]int{
-//            "B2": 2, "C2": 3, "D2": 3, "B3": 5, "C3": 2, "D3": 4, "B4": 6, "C4": 7, "D4": 8}
+//        categories := map[string]string{"A2": "Small", "A3": "Normal", "A4": "Large", "B1": "Apple", "C1": "Orange", "D1": "Pear"}
+//        values := map[string]int{"B2": 2, "C2": 3, "D2": 3, "B3": 5, "C3": 2, "D3": 4, "B4": 6, "C4": 7, "D4": 8}
 //        f := excelize.NewFile()
 //        for k, v := range categories {
 //            f.SetCellValue("Sheet1", k, v)
@@ -796,89 +719,11 @@ func parseFormatChartSet(formatSet string) (*formatChart, error) {
 //        for k, v := range values {
 //            f.SetCellValue("Sheet1", k, v)
 //        }
-//        if err := f.AddChart("Sheet1", "E1", `{
-//            "type": "col",
-//            "series": [
-//            {
-//                "name": "Sheet1!$A$2",
-//                "categories": "",
-//                "values": "Sheet1!$B$2:$D$2"
-//            },
-//            {
-//                "name": "Sheet1!$A$3",
-//                "categories": "Sheet1!$B$1:$D$1",
-//                "values": "Sheet1!$B$3:$D$3"
-//            }],
-//            "format":
-//            {
-//                "x_scale": 1.0,
-//                "y_scale": 1.0,
-//                "x_offset": 15,
-//                "y_offset": 10,
-//                "print_obj": true,
-//                "lock_aspect_ratio": false,
-//                "locked": false
-//            },
-//            "title":
-//            {
-//                "name": "Clustered Column - Line Chart"
-//            },
-//            "legend":
-//            {
-//                "position": "left",
-//                "show_legend_key": false
-//            },
-//            "plotarea":
-//            {
-//                "show_bubble_size": true,
-//                "show_cat_name": false,
-//                "show_leader_lines": false,
-//                "show_percent": true,
-//                "show_series_name": true,
-//                "show_val": true
-//            }
-//        }`, `{
-//            "type": "line",
-//            "series": [
-//            {
-//                "name": "Sheet1!$A$4",
-//                "categories": "Sheet1!$B$1:$D$1",
-//                "values": "Sheet1!$B$4:$D$4",
-//                "marker":
-//                {
-//                    "symbol": "none",
-//                    "size": 10
-//                }
-//            }],
-//            "format":
-//            {
-//                "x_scale": 1,
-//                "y_scale": 1,
-//                "x_offset": 15,
-//                "y_offset": 10,
-//                "print_obj": true,
-//                "lock_aspect_ratio": false,
-//                "locked": false
-//            },
-//            "legend":
-//            {
-//                "position": "right",
-//                "show_legend_key": false
-//            },
-//            "plotarea":
-//            {
-//                "show_bubble_size": true,
-//                "show_cat_name": false,
-//                "show_leader_lines": false,
-//                "show_percent": true,
-//                "show_series_name": true,
-//                "show_val": true
-//            }
-//        }`); err != nil {
+//        if err := f.AddChart("Sheet1", "E1", `{"type":"col","series":[{"name":"Sheet1!$A$2","categories":"","values":"Sheet1!$B$2:$D$2"},{"name":"Sheet1!$A$3","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$3:$D$3"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"Clustered Column - Line Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true}}`, `{"type":"line","series":[{"name":"Sheet1!$A$4","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$4:$D$4"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true}}`); err != nil {
 //            fmt.Println(err)
 //            return
 //        }
-//        // Save spreadsheet file by the given path.
+//        // Save xlsx file by the given path.
 //        if err := f.SaveAs("Book1.xlsx"); err != nil {
 //            fmt.Println(err)
 //        }
@@ -886,7 +731,7 @@ func parseFormatChartSet(formatSet string) (*formatChart, error) {
 //
 func (f *File) AddChart(sheet, cell, format string, combo ...string) error {
 	// Read sheet data.
-	ws, err := f.workSheetReader(sheet)
+	xlsx, err := f.workSheetReader(sheet)
 	if err != nil {
 		return err
 	}
@@ -898,7 +743,7 @@ func (f *File) AddChart(sheet, cell, format string, combo ...string) error {
 	drawingID := f.countDrawings() + 1
 	chartID := f.countCharts() + 1
 	drawingXML := "xl/drawings/drawing" + strconv.Itoa(drawingID) + ".xml"
-	drawingID, drawingXML = f.prepareDrawing(ws, drawingID, sheet, drawingXML)
+	drawingID, drawingXML = f.prepareDrawing(xlsx, drawingID, sheet, drawingXML)
 	drawingRels := "xl/drawings/_rels/drawing" + strconv.Itoa(drawingID) + ".xml.rels"
 	drawingRID := f.addRels(drawingRels, SourceRelationshipChart, "../charts/chart"+strconv.Itoa(chartID)+".xml", "")
 	err = f.addDrawingChart(sheet, drawingXML, cell, formatSet.Dimension.Width, formatSet.Dimension.Height, drawingRID, &formatSet.Format)
@@ -908,7 +753,6 @@ func (f *File) AddChart(sheet, cell, format string, combo ...string) error {
 	f.addChart(formatSet, comboCharts)
 	f.addContentTypePart(chartID, "chart")
 	f.addContentTypePart(drawingID, "drawings")
-	f.addSheetNameSpace(sheet, SourceRelationship)
 	return err
 }
 
@@ -918,16 +762,16 @@ func (f *File) AddChart(sheet, cell, format string, combo ...string) error {
 // a chart.
 func (f *File) AddChartSheet(sheet, format string, combo ...string) error {
 	// Check if the worksheet already exists
-	if f.GetSheetIndex(sheet) != -1 {
-		return ErrExistsWorksheet
+	if f.GetSheetIndex(sheet) != 0 {
+		return errors.New("the same name worksheet already exists")
 	}
 	formatSet, comboCharts, err := f.getFormatChart(format, combo)
 	if err != nil {
 		return err
 	}
 	cs := xlsxChartsheet{
-		SheetViews: &xlsxChartsheetViews{
-			SheetView: []*xlsxChartsheetView{{ZoomScaleAttr: 100, ZoomToFitAttr: true}},
+		SheetViews: []*xlsxChartsheetViews{{
+			SheetView: []*xlsxChartsheetView{{ZoomScaleAttr: 100, ZoomToFitAttr: true}}},
 		},
 	}
 	f.SheetCount++
@@ -953,13 +797,12 @@ func (f *File) AddChartSheet(sheet, format string, combo ...string) error {
 	f.addContentTypePart(chartID, "chart")
 	f.addContentTypePart(sheetID, "chartsheet")
 	f.addContentTypePart(drawingID, "drawings")
-	// Update workbook.xml.rels
-	rID := f.addRels(f.getWorkbookRelsPath(), SourceRelationshipChartsheet, fmt.Sprintf("/xl/chartsheets/sheet%d.xml", sheetID), "")
-	// Update workbook.xml
+	// Update xl/_rels/workbook.xml.rels
+	rID := f.addRels("xl/_rels/workbook.xml.rels", SourceRelationshipChartsheet, fmt.Sprintf("chartsheets/sheet%d.xml", sheetID), "")
+	// Update xl/workbook.xml
 	f.setWorkbook(sheet, sheetID, rID)
 	chartsheet, _ := xml.Marshal(cs)
-	f.addSheetNameSpace(sheet, NameSpaceSpreadSheet)
-	f.saveFileList(path, replaceRelationshipsBytes(f.replaceNameSpaceBytes(path, chartsheet)))
+	f.saveFileList(path, replaceRelationshipsBytes(replaceRelationshipsNameSpaceBytes(chartsheet)))
 	return err
 }
 

+ 21 - 136
chart_test.go

@@ -11,8 +11,8 @@ import (
 )
 
 func TestChartSize(t *testing.T) {
-	f := NewFile()
-	sheet1 := f.GetSheetName(0)
+	xlsx := NewFile()
+	sheet1 := xlsx.GetSheetName(1)
 
 	categories := map[string]string{
 		"A2": "Small",
@@ -23,7 +23,7 @@ func TestChartSize(t *testing.T) {
 		"D1": "Pear",
 	}
 	for cell, v := range categories {
-		assert.NoError(t, f.SetCellValue(sheet1, cell, v))
+		assert.NoError(t, xlsx.SetCellValue(sheet1, cell, v))
 	}
 
 	values := map[string]int{
@@ -38,10 +38,10 @@ func TestChartSize(t *testing.T) {
 		"D4": 8,
 	}
 	for cell, v := range values {
-		assert.NoError(t, f.SetCellValue(sheet1, cell, v))
+		assert.NoError(t, xlsx.SetCellValue(sheet1, cell, v))
 	}
 
-	assert.NoError(t, f.AddChart("Sheet1", "E4", `{"type":"col3DClustered","dimension":{"width":640, "height":480},`+
+	assert.NoError(t, xlsx.AddChart("Sheet1", "E4", `{"type":"col3DClustered","dimension":{"width":640, "height":480},`+
 		`"series":[{"name":"Sheet1!$A$2","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$2:$D$2"},`+
 		`{"name":"Sheet1!$A$3","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$3:$D$3"},`+
 		`{"name":"Sheet1!$A$4","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$4:$D$4"}],`+
@@ -49,8 +49,8 @@ func TestChartSize(t *testing.T) {
 
 	var buffer bytes.Buffer
 
-	// Save spreadsheet by the given path.
-	assert.NoError(t, f.Write(&buffer))
+	// Save xlsx file by the given path.
+	assert.NoError(t, xlsx.Write(&buffer))
 
 	newFile, err := OpenReader(&buffer)
 	assert.NoError(t, err)
@@ -116,7 +116,7 @@ func TestAddChart(t *testing.T) {
 	// Test add chart on not exists worksheet.
 	assert.EqualError(t, f.AddChart("SheetN", "P1", "{}"), "sheet SheetN is not exist")
 
-	assert.NoError(t, f.AddChart("Sheet1", "P1", `{"type":"col","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"none":true,"show_legend_key":true},"title":{"name":"2D Column Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
+	assert.NoError(t, f.AddChart("Sheet1", "P1", `{"type":"col","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"2D Column Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
 	assert.NoError(t, f.AddChart("Sheet1", "X1", `{"type":"colStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"2D Stacked Column Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
 	assert.NoError(t, f.AddChart("Sheet1", "P16", `{"type":"colPercentStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"100% Stacked Column Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
 	assert.NoError(t, f.AddChart("Sheet1", "X16", `{"type":"col3DClustered","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"bottom","show_legend_key":false},"title":{"name":"3D Clustered Column Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
@@ -138,7 +138,7 @@ func TestAddChart(t *testing.T) {
 	assert.NoError(t, f.AddChart("Sheet2", "P1", `{"type":"radar","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"top_right","show_legend_key":false},"title":{"name":"Radar Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"span"}`))
 	assert.NoError(t, f.AddChart("Sheet2", "X1", `{"type":"scatter","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"bottom","show_legend_key":false},"title":{"name":"Scatter Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
 	assert.NoError(t, f.AddChart("Sheet2", "P16", `{"type":"doughnut","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$A$30:$D$37","values":"Sheet1!$B$30:$B$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"right","show_legend_key":false},"title":{"name":"Doughnut Chart"},"plotarea":{"show_bubble_size":false,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":false,"show_val":false},"show_blanks_as":"zero"}`))
-	assert.NoError(t, f.AddChart("Sheet2", "X16", `{"type":"line","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30","marker":{"symbol":"none","size":10}},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37","line":{"width":0.25}}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"top","show_legend_key":false},"title":{"name":"Line Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero","x_axis":{"major_grid_lines":true,"minor_grid_lines":true,"tick_label_skip":1},"y_axis":{"major_grid_lines":true,"minor_grid_lines":true,"major_unit":1}}`))
+	assert.NoError(t, f.AddChart("Sheet2", "X16", `{"type":"line","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37","line":{"width":0.25}}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"top","show_legend_key":false},"title":{"name":"Line Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero","x_axis":{"major_grid_lines":true,"minor_grid_lines":true,"tick_label_skip":1},"y_axis":{"major_grid_lines":true,"minor_grid_lines":true,"major_unit":1}}`))
 	assert.NoError(t, f.AddChart("Sheet2", "P32", `{"type":"pie3D","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$A$30:$D$37","values":"Sheet1!$B$30:$B$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"bottom","show_legend_key":false},"title":{"name":"3D Pie Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":false,"show_val":false},"show_blanks_as":"zero"}`))
 	assert.NoError(t, f.AddChart("Sheet2", "X32", `{"type":"pie","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$A$30:$D$37","values":"Sheet1!$B$30:$B$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"bottom","show_legend_key":false},"title":{"name":"Pie Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":false,"show_val":false},"show_blanks_as":"gap"}`))
 	assert.NoError(t, f.AddChart("Sheet2", "P48", `{"type":"bar","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"2D Clustered Bar Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
@@ -179,14 +179,14 @@ func TestAddChart(t *testing.T) {
 	assert.NoError(t, f.AddChart("Sheet2", "BD64", `{"type":"barOfPie","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$A$30:$D$37","values":"Sheet1!$B$30:$B$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"Bar of Pie Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero","x_axis":{"major_grid_lines":true},"y_axis":{"major_grid_lines":true}}`))
 	// combo chart
 	f.NewSheet("Combo Charts")
-	clusteredColumnCombo := [][]string{
-		{"A1", "line", "Clustered Column - Line Chart"},
-		{"I1", "bubble", "Clustered Column - Bubble Chart"},
-		{"Q1", "bubble3D", "Clustered Column - Bubble 3D Chart"},
-		{"Y1", "doughnut", "Clustered Column - Doughnut Chart"},
+	clusteredColumnCombo := map[string][]string{
+		"A1": {"line", "Clustered Column - Line Chart"},
+		"I1": {"bubble", "Clustered Column - Bubble Chart"},
+		"Q1": {"bubble3D", "Clustered Column - Bubble 3D Chart"},
+		"Y1": {"doughnut", "Clustered Column - Doughnut Chart"},
 	}
-	for _, props := range clusteredColumnCombo {
-		assert.NoError(t, f.AddChart("Combo Charts", props[0], fmt.Sprintf(`{"type":"col","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"%s"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true}}`, props[2]), fmt.Sprintf(`{"type":"%s","series":[{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true}}`, props[1])))
+	for axis, props := range clusteredColumnCombo {
+		assert.NoError(t, f.AddChart("Combo Charts", axis, fmt.Sprintf(`{"type":"col","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"%s"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true}}`, props[1]), fmt.Sprintf(`{"type":"%s","series":[{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true}}`, props[0])))
 	}
 	stackedAreaCombo := map[string][]string{
 		"A16": {"line", "Stacked Area - Line Chart"},
@@ -220,24 +220,22 @@ func TestAddChartSheet(t *testing.T) {
 	}
 	assert.NoError(t, f.AddChartSheet("Chart1", `{"type":"col3DClustered","series":[{"name":"Sheet1!$A$2","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$2:$D$2"},{"name":"Sheet1!$A$3","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$3:$D$3"},{"name":"Sheet1!$A$4","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$4:$D$4"}],"title":{"name":"Fruit 3D Clustered Column Chart"}}`))
 	// Test set the chartsheet as active sheet
-	var sheetIdx int
-	for idx, sheetName := range f.GetSheetList() {
+	var sheetID int
+	for idx, sheetName := range f.GetSheetMap() {
 		if sheetName != "Chart1" {
 			continue
 		}
-		sheetIdx = idx
+		sheetID = idx
 	}
-	f.SetActiveSheet(sheetIdx)
+	f.SetActiveSheet(sheetID)
 
 	// Test cell value on chartsheet
 	assert.EqualError(t, f.SetCellValue("Chart1", "A1", true), "sheet Chart1 is chart sheet")
 	// Test add chartsheet on already existing name sheet
-	assert.EqualError(t, f.AddChartSheet("Sheet1", `{"type":"col3DClustered","series":[{"name":"Sheet1!$A$2","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$2:$D$2"},{"name":"Sheet1!$A$3","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$3:$D$3"},{"name":"Sheet1!$A$4","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$4:$D$4"}],"title":{"name":"Fruit 3D Clustered Column Chart"}}`), ErrExistsWorksheet.Error())
+	assert.EqualError(t, f.AddChartSheet("Sheet1", `{"type":"col3DClustered","series":[{"name":"Sheet1!$A$2","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$2:$D$2"},{"name":"Sheet1!$A$3","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$3:$D$3"},{"name":"Sheet1!$A$4","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$4:$D$4"}],"title":{"name":"Fruit 3D Clustered Column Chart"}}`), "the same name worksheet already exists")
 	// Test with unsupported chart type
 	assert.EqualError(t, f.AddChartSheet("Chart2", `{"type":"unknown","series":[{"name":"Sheet1!$A$2","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$2:$D$2"},{"name":"Sheet1!$A$3","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$3:$D$3"},{"name":"Sheet1!$A$4","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$4:$D$4"}],"title":{"name":"Fruit 3D Clustered Column Chart"}}`), "unsupported chart type unknown")
 
-	assert.NoError(t, f.UpdateLinkedValue())
-
 	assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddChartSheet.xlsx")))
 }
 
@@ -255,116 +253,3 @@ func TestDeleteChart(t *testing.T) {
 	// Test delete chart on no chart worksheet.
 	assert.NoError(t, NewFile().DeleteChart("Sheet1", "A1"))
 }
-
-func TestChartWithLogarithmicBase(t *testing.T) {
-	// Create test XLSX file with data
-	f := NewFile()
-	sheet1 := f.GetSheetName(0)
-	categories := map[string]float64{
-		"A1":  1,
-		"A2":  2,
-		"A3":  3,
-		"A4":  4,
-		"A5":  5,
-		"A6":  6,
-		"A7":  7,
-		"A8":  8,
-		"A9":  9,
-		"A10": 10,
-		"B1":  0.1,
-		"B2":  1,
-		"B3":  2,
-		"B4":  3,
-		"B5":  20,
-		"B6":  30,
-		"B7":  100,
-		"B8":  500,
-		"B9":  700,
-		"B10": 5000,
-	}
-	for cell, v := range categories {
-		assert.NoError(t, f.SetCellValue(sheet1, cell, v))
-	}
-
-	// Add two chart, one without and one with log scaling
-	assert.NoError(t, f.AddChart(sheet1, "C1",
-		`{"type":"line","dimension":{"width":640, "height":480},`+
-			`"series":[{"name":"value","categories":"Sheet1!$A$1:$A$19","values":"Sheet1!$B$1:$B$10"}],`+
-			`"title":{"name":"Line chart without log scaling"}}`))
-	assert.NoError(t, f.AddChart(sheet1, "M1",
-		`{"type":"line","dimension":{"width":640, "height":480},`+
-			`"series":[{"name":"value","categories":"Sheet1!$A$1:$A$19","values":"Sheet1!$B$1:$B$10"}],`+
-			`"y_axis":{"logbase":10.5},`+
-			`"title":{"name":"Line chart with log 10 scaling"}}`))
-	assert.NoError(t, f.AddChart(sheet1, "A25",
-		`{"type":"line","dimension":{"width":320, "height":240},`+
-			`"series":[{"name":"value","categories":"Sheet1!$A$1:$A$19","values":"Sheet1!$B$1:$B$10"}],`+
-			`"y_axis":{"logbase":1.9},`+
-			`"title":{"name":"Line chart with log 1.9 scaling"}}`))
-	assert.NoError(t, f.AddChart(sheet1, "F25",
-		`{"type":"line","dimension":{"width":320, "height":240},`+
-			`"series":[{"name":"value","categories":"Sheet1!$A$1:$A$19","values":"Sheet1!$B$1:$B$10"}],`+
-			`"y_axis":{"logbase":2},`+
-			`"title":{"name":"Line chart with log 2 scaling"}}`))
-	assert.NoError(t, f.AddChart(sheet1, "K25",
-		`{"type":"line","dimension":{"width":320, "height":240},`+
-			`"series":[{"name":"value","categories":"Sheet1!$A$1:$A$19","values":"Sheet1!$B$1:$B$10"}],`+
-			`"y_axis":{"logbase":1000.1},`+
-			`"title":{"name":"Line chart with log 1000.1 scaling"}}`))
-	assert.NoError(t, f.AddChart(sheet1, "P25",
-		`{"type":"line","dimension":{"width":320, "height":240},`+
-			`"series":[{"name":"value","categories":"Sheet1!$A$1:$A$19","values":"Sheet1!$B$1:$B$10"}],`+
-			`"y_axis":{"logbase":1000},`+
-			`"title":{"name":"Line chart with log 1000 scaling"}}`))
-
-	// Export XLSX file for human confirmation
-	assert.NoError(t, f.SaveAs(filepath.Join("test", "TestChartWithLogarithmicBase10.xlsx")))
-
-	// Write the XLSX file to a buffer
-	var buffer bytes.Buffer
-	assert.NoError(t, f.Write(&buffer))
-
-	// Read back the XLSX file from the buffer
-	newFile, err := OpenReader(&buffer)
-	assert.NoError(t, err)
-
-	// Check the number of charts
-	expectedChartsCount := 6
-	chartsNum := newFile.countCharts()
-	if !assert.Equal(t, expectedChartsCount, chartsNum,
-		"Expected %d charts, actual %d", expectedChartsCount, chartsNum) {
-		t.FailNow()
-	}
-
-	chartSpaces := make([]xlsxChartSpace, expectedChartsCount)
-	type xmlChartContent []byte
-	xmlCharts := make([]xmlChartContent, expectedChartsCount)
-	expectedChartsLogBase := []float64{0, 10.5, 0, 2, 0, 1000}
-	var ok bool
-
-	for i := 0; i < expectedChartsCount; i++ {
-		chartPath := fmt.Sprintf("xl/charts/chart%d.xml", i+1)
-		xmlCharts[i], ok = newFile.XLSX[chartPath]
-		assert.True(t, ok, "Can't open the %s", chartPath)
-
-		err = xml.Unmarshal([]byte(xmlCharts[i]), &chartSpaces[i])
-		if !assert.NoError(t, err) {
-			t.FailNow()
-		}
-
-		chartLogBasePtr := chartSpaces[i].Chart.PlotArea.ValAx[0].Scaling.LogBase
-		if expectedChartsLogBase[i] == 0 {
-			if !assert.Nil(t, chartLogBasePtr, "LogBase is not nil") {
-				t.FailNow()
-			}
-		} else {
-			if !assert.NotNil(t, chartLogBasePtr, "LogBase is nil") {
-				t.FailNow()
-			}
-			if !assert.Equal(t, expectedChartsLogBase[i], *(chartLogBasePtr.Val),
-				"Expected log base to %f, actual %f", expectedChartsLogBase[i], *(chartLogBasePtr.Val)) {
-				t.FailNow()
-			}
-		}
-	}
-}

+ 99 - 276
col.go

@@ -1,21 +1,17 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
+// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
 // this source code is governed by a BSD-style license that can be found in
 // the LICENSE file.
 //
 // Package excelize providing a set of functions that allow you to write to
-// and read from XLSX / XLSM / XLTM files. Supports reading and writing
-// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
-// complex components by high compatibility, and provided streaming API for
-// generating or reading data from a worksheet with huge amounts of data. This
-// library needs Go version 1.15 or later.
+// and read from XLSX files. Support reads and writes XLSX file generated by
+// Microsoft Excel™ 2007 and later. Support save file without losing original
+// charts of XLSX. This library needs Go version 1.10 or later.
 
 package excelize
 
 import (
-	"bytes"
-	"encoding/xml"
+	"errors"
 	"math"
-	"strconv"
 	"strings"
 
 	"github.com/mohae/deepcopy"
@@ -23,211 +19,11 @@ import (
 
 // Define the default cell size and EMU unit of measurement.
 const (
-	defaultColWidth        float64 = 9.140625
 	defaultColWidthPixels  float64 = 64
-	defaultRowHeight       float64 = 15
 	defaultRowHeightPixels float64 = 20
 	EMU                    int     = 9525
 )
 
-// Cols defines an iterator to a sheet
-type Cols struct {
-	err                                  error
-	curCol, totalCol, stashCol, totalRow int
-	sheet                                string
-	f                                    *File
-	sheetXML                             []byte
-}
-
-// GetCols return all the columns in a sheet by given worksheet name (case
-// sensitive). For example:
-//
-//    cols, err := f.GetCols("Sheet1")
-//    if err != nil {
-//        fmt.Println(err)
-//        return
-//    }
-//    for _, col := range cols {
-//        for _, rowCell := range col {
-//            fmt.Print(rowCell, "\t")
-//        }
-//        fmt.Println()
-//    }
-//
-func (f *File) GetCols(sheet string) ([][]string, error) {
-	cols, err := f.Cols(sheet)
-	if err != nil {
-		return nil, err
-	}
-	results := make([][]string, 0, 64)
-	for cols.Next() {
-		col, _ := cols.Rows()
-		results = append(results, col)
-	}
-	return results, nil
-}
-
-// Next will return true if the next column is found.
-func (cols *Cols) Next() bool {
-	cols.curCol++
-	return cols.curCol <= cols.totalCol
-}
-
-// Error will return an error when the error occurs.
-func (cols *Cols) Error() error {
-	return cols.err
-}
-
-// Rows return the current column's row values.
-func (cols *Cols) Rows() ([]string, error) {
-	var (
-		err              error
-		inElement        string
-		cellCol, cellRow int
-		rows             []string
-	)
-	if cols.stashCol >= cols.curCol {
-		return rows, err
-	}
-	d := cols.f.sharedStringsReader()
-	decoder := cols.f.xmlNewDecoder(bytes.NewReader(cols.sheetXML))
-	for {
-		token, _ := decoder.Token()
-		if token == nil {
-			break
-		}
-		switch xmlElement := token.(type) {
-		case xml.StartElement:
-			inElement = xmlElement.Name.Local
-			if inElement == "row" {
-				cellCol = 0
-				cellRow++
-				attrR, _ := attrValToInt("r", xmlElement.Attr)
-				if attrR != 0 {
-					cellRow = attrR
-				}
-			}
-			if inElement == "c" {
-				cellCol++
-				for _, attr := range xmlElement.Attr {
-					if attr.Name.Local == "r" {
-						if cellCol, cellRow, err = CellNameToCoordinates(attr.Value); err != nil {
-							return rows, err
-						}
-					}
-				}
-				blank := cellRow - len(rows)
-				for i := 1; i < blank; i++ {
-					rows = append(rows, "")
-				}
-				if cellCol == cols.curCol {
-					colCell := xlsxC{}
-					_ = decoder.DecodeElement(&colCell, &xmlElement)
-					val, _ := colCell.getValueFrom(cols.f, d)
-					rows = append(rows, val)
-				}
-			}
-		case xml.EndElement:
-			if xmlElement.Name.Local == "sheetData" {
-				return rows, err
-			}
-		}
-	}
-	return rows, err
-}
-
-// columnXMLIterator defined runtime use field for the worksheet column SAX parser.
-type columnXMLIterator struct {
-	err                  error
-	cols                 Cols
-	cellCol, curRow, row int
-}
-
-// columnXMLHandler parse the column XML element of the worksheet.
-func columnXMLHandler(colIterator *columnXMLIterator, xmlElement *xml.StartElement) {
-	colIterator.err = nil
-	inElement := xmlElement.Name.Local
-	if inElement == "row" {
-		colIterator.row++
-		for _, attr := range xmlElement.Attr {
-			if attr.Name.Local == "r" {
-				if colIterator.curRow, colIterator.err = strconv.Atoi(attr.Value); colIterator.err != nil {
-					return
-				}
-				colIterator.row = colIterator.curRow
-			}
-		}
-		colIterator.cols.totalRow = colIterator.row
-		colIterator.cellCol = 0
-	}
-	if inElement == "c" {
-		colIterator.cellCol++
-		for _, attr := range xmlElement.Attr {
-			if attr.Name.Local == "r" {
-				if colIterator.cellCol, _, colIterator.err = CellNameToCoordinates(attr.Value); colIterator.err != nil {
-					return
-				}
-			}
-		}
-		if colIterator.cellCol > colIterator.cols.totalCol {
-			colIterator.cols.totalCol = colIterator.cellCol
-		}
-	}
-}
-
-// Cols returns a columns iterator, used for streaming reading data for a
-// worksheet with a large data. For example:
-//
-//    cols, err := f.Cols("Sheet1")
-//    if err != nil {
-//        fmt.Println(err)
-//        return
-//    }
-//    for cols.Next() {
-//        col, err := cols.Rows()
-//        if err != nil {
-//            fmt.Println(err)
-//        }
-//        for _, rowCell := range col {
-//            fmt.Print(rowCell, "\t")
-//        }
-//        fmt.Println()
-//    }
-//
-func (f *File) Cols(sheet string) (*Cols, error) {
-	name, ok := f.sheetMap[trimSheetName(sheet)]
-	if !ok {
-		return nil, ErrSheetNotExist{sheet}
-	}
-	if f.Sheet[name] != nil {
-		output, _ := xml.Marshal(f.Sheet[name])
-		f.saveFileList(name, f.replaceNameSpaceBytes(name, output))
-	}
-	var colIterator columnXMLIterator
-	colIterator.cols.sheetXML = f.readXML(name)
-	decoder := f.xmlNewDecoder(bytes.NewReader(colIterator.cols.sheetXML))
-	for {
-		token, _ := decoder.Token()
-		if token == nil {
-			break
-		}
-		switch xmlElement := token.(type) {
-		case xml.StartElement:
-			columnXMLHandler(&colIterator, &xmlElement)
-			if colIterator.err != nil {
-				return &colIterator.cols, colIterator.err
-			}
-		case xml.EndElement:
-			if xmlElement.Name.Local == "sheetData" {
-				colIterator.cols.f = f
-				colIterator.cols.sheet = trimSheetName(sheet)
-				return &colIterator.cols, nil
-			}
-		}
-	}
-	return &colIterator.cols, nil
-}
-
 // GetColVisible provides a function to get visible of a single column by given
 // worksheet name and column name. For example, get visible state of column D
 // in Sheet1:
@@ -241,16 +37,16 @@ func (f *File) GetColVisible(sheet, col string) (bool, error) {
 		return visible, err
 	}
 
-	ws, err := f.workSheetReader(sheet)
+	xlsx, err := f.workSheetReader(sheet)
 	if err != nil {
 		return false, err
 	}
-	if ws.Cols == nil {
+	if xlsx.Cols == nil {
 		return visible, err
 	}
 
-	for c := range ws.Cols.Col {
-		colData := &ws.Cols.Col[c]
+	for c := range xlsx.Cols.Col {
+		colData := &xlsx.Cols.Col[c]
 		if colData.Min <= colNum && colNum <= colData.Max {
 			visible = !colData.Hidden
 		}
@@ -270,28 +66,42 @@ func (f *File) GetColVisible(sheet, col string) (bool, error) {
 //    err := f.SetColVisible("Sheet1", "D:F", false)
 //
 func (f *File) SetColVisible(sheet, columns string, visible bool) error {
-	start, end, err := f.parseColRange(columns)
+	var max int
+
+	colsTab := strings.Split(columns, ":")
+	min, err := ColumnNameToNumber(colsTab[0])
 	if err != nil {
 		return err
 	}
-	ws, err := f.workSheetReader(sheet)
+	if len(colsTab) == 2 {
+		max, err = ColumnNameToNumber(colsTab[1])
+		if err != nil {
+			return err
+		}
+	} else {
+		max = min
+	}
+	if max < min {
+		min, max = max, min
+	}
+	xlsx, err := f.workSheetReader(sheet)
 	if err != nil {
 		return err
 	}
 	colData := xlsxCol{
-		Min:         start,
-		Max:         end,
-		Width:       defaultColWidth, // default width
+		Min:         min,
+		Max:         max,
+		Width:       9, // default width
 		Hidden:      !visible,
 		CustomWidth: true,
 	}
-	if ws.Cols == nil {
+	if xlsx.Cols == nil {
 		cols := xlsxCols{}
 		cols.Col = append(cols.Col, colData)
-		ws.Cols = &cols
+		xlsx.Cols = &cols
 		return nil
 	}
-	ws.Cols.Col = flatCols(colData, ws.Cols.Col, func(fc, c xlsxCol) xlsxCol {
+	xlsx.Cols.Col = flatCols(colData, xlsx.Cols.Col, func(fc, c xlsxCol) xlsxCol {
 		fc.BestFit = c.BestFit
 		fc.Collapsed = c.Collapsed
 		fc.CustomWidth = c.CustomWidth
@@ -316,15 +126,15 @@ func (f *File) GetColOutlineLevel(sheet, col string) (uint8, error) {
 	if err != nil {
 		return level, err
 	}
-	ws, err := f.workSheetReader(sheet)
+	xlsx, err := f.workSheetReader(sheet)
 	if err != nil {
 		return 0, err
 	}
-	if ws.Cols == nil {
+	if xlsx.Cols == nil {
 		return level, err
 	}
-	for c := range ws.Cols.Col {
-		colData := &ws.Cols.Col[c]
+	for c := range xlsx.Cols.Col {
+		colData := &xlsx.Cols.Col[c]
 		if colData.Min <= colNum && colNum <= colData.Max {
 			level = colData.OutlineLevel
 		}
@@ -332,25 +142,6 @@ func (f *File) GetColOutlineLevel(sheet, col string) (uint8, error) {
 	return level, err
 }
 
-// parseColRange parse and convert column range with column name to the column number.
-func (f *File) parseColRange(columns string) (start, end int, err error) {
-	colsTab := strings.Split(columns, ":")
-	start, err = ColumnNameToNumber(colsTab[0])
-	if err != nil {
-		return
-	}
-	end = start
-	if len(colsTab) == 2 {
-		if end, err = ColumnNameToNumber(colsTab[1]); err != nil {
-			return
-		}
-	}
-	if end < start {
-		start, end = end, start
-	}
-	return
-}
-
 // SetColOutlineLevel provides a function to set outline level of a single
 // column by given worksheet name and column name. The value of parameter
 // 'level' is 1-7. For example, set outline level of column D in Sheet1 to 2:
@@ -359,7 +150,7 @@ func (f *File) parseColRange(columns string) (start, end int, err error) {
 //
 func (f *File) SetColOutlineLevel(sheet, col string, level uint8) error {
 	if level > 7 || level < 1 {
-		return ErrOutlineLevel
+		return errors.New("invalid outline level")
 	}
 	colNum, err := ColumnNameToNumber(col)
 	if err != nil {
@@ -371,17 +162,17 @@ func (f *File) SetColOutlineLevel(sheet, col string, level uint8) error {
 		OutlineLevel: level,
 		CustomWidth:  true,
 	}
-	ws, err := f.workSheetReader(sheet)
+	xlsx, err := f.workSheetReader(sheet)
 	if err != nil {
 		return err
 	}
-	if ws.Cols == nil {
+	if xlsx.Cols == nil {
 		cols := xlsxCols{}
 		cols.Col = append(cols.Col, colData)
-		ws.Cols = &cols
+		xlsx.Cols = &cols
 		return err
 	}
-	ws.Cols.Col = flatCols(colData, ws.Cols.Col, func(fc, c xlsxCol) xlsxCol {
+	xlsx.Cols.Col = flatCols(colData, xlsx.Cols.Col, func(fc, c xlsxCol) xlsxCol {
 		fc.BestFit = c.BestFit
 		fc.Collapsed = c.Collapsed
 		fc.CustomWidth = c.CustomWidth
@@ -406,23 +197,39 @@ func (f *File) SetColOutlineLevel(sheet, col string, level uint8) error {
 //    err = f.SetColStyle("Sheet1", "C:F", style)
 //
 func (f *File) SetColStyle(sheet, columns string, styleID int) error {
-	start, end, err := f.parseColRange(columns)
+	xlsx, err := f.workSheetReader(sheet)
 	if err != nil {
 		return err
 	}
-	ws, err := f.workSheetReader(sheet)
+	var c1, c2 string
+	var min, max int
+	cols := strings.Split(columns, ":")
+	c1 = cols[0]
+	min, err = ColumnNameToNumber(c1)
 	if err != nil {
 		return err
 	}
-	if ws.Cols == nil {
-		ws.Cols = &xlsxCols{}
+	if len(cols) == 2 {
+		c2 = cols[1]
+		max, err = ColumnNameToNumber(c2)
+		if err != nil {
+			return err
+		}
+	} else {
+		max = min
+	}
+	if max < min {
+		min, max = max, min
+	}
+	if xlsx.Cols == nil {
+		xlsx.Cols = &xlsxCols{}
 	}
-	ws.Cols.Col = flatCols(xlsxCol{
-		Min:   start,
-		Max:   end,
-		Width: defaultColWidth,
+	xlsx.Cols.Col = flatCols(xlsxCol{
+		Min:   min,
+		Max:   max,
+		Width: 9,
 		Style: styleID,
-	}, ws.Cols.Col, func(fc, c xlsxCol) xlsxCol {
+	}, xlsx.Cols.Col, func(fc, c xlsxCol) xlsxCol {
 		fc.BestFit = c.BestFit
 		fc.Collapsed = c.Collapsed
 		fc.CustomWidth = c.CustomWidth
@@ -450,14 +257,11 @@ func (f *File) SetColWidth(sheet, startcol, endcol string, width float64) error
 	if err != nil {
 		return err
 	}
-	if width > MaxColumnWidth {
-		return ErrColumnWidth
-	}
 	if min > max {
 		min, max = max, min
 	}
 
-	ws, err := f.workSheetReader(sheet)
+	xlsx, err := f.workSheetReader(sheet)
 	if err != nil {
 		return err
 	}
@@ -467,13 +271,13 @@ func (f *File) SetColWidth(sheet, startcol, endcol string, width float64) error
 		Width:       width,
 		CustomWidth: true,
 	}
-	if ws.Cols == nil {
+	if xlsx.Cols == nil {
 		cols := xlsxCols{}
 		cols.Col = append(cols.Col, col)
-		ws.Cols = &cols
+		xlsx.Cols = &cols
 		return err
 	}
-	ws.Cols.Col = flatCols(col, ws.Cols.Col, func(fc, c xlsxCol) xlsxCol {
+	xlsx.Cols.Col = flatCols(col, xlsx.Cols.Col, func(fc, c xlsxCol) xlsxCol {
 		fc.BestFit = c.BestFit
 		fc.Collapsed = c.Collapsed
 		fc.Hidden = c.Hidden
@@ -565,7 +369,26 @@ func flatCols(col xlsxCol, cols []xlsxCol, replacer func(fc, c xlsxCol) xlsxCol)
 //    width           # Width of object frame.
 //    height          # Height of object frame.
 //
-func (f *File) positionObjectPixels(sheet string, col, row, x1, y1, width, height int) (int, int, int, int, int, int) {
+//    xAbs            # Absolute distance to left side of object.
+//    yAbs            # Absolute distance to top side of object.
+//
+func (f *File) positionObjectPixels(sheet string, col, row, x1, y1, width, height int) (int, int, int, int, int, int, int, int) {
+	xAbs := 0
+	yAbs := 0
+
+	// Calculate the absolute x offset of the top-left vertex.
+	for colID := 1; colID <= col; colID++ {
+		xAbs += f.getColWidth(sheet, colID)
+	}
+	xAbs += x1
+
+	// Calculate the absolute y offset of the top-left vertex.
+	// Store the column change to allow optimisations.
+	for rowID := 1; rowID <= row; rowID++ {
+		yAbs += f.getRowHeight(sheet, rowID)
+	}
+	yAbs += y1
+
 	// Adjust start column for offsets that are greater than the col width.
 	for x1 >= f.getColWidth(sheet, col) {
 		x1 -= f.getColWidth(sheet, col)
@@ -600,7 +423,7 @@ func (f *File) positionObjectPixels(sheet string, col, row, x1, y1, width, heigh
 	// The end vertices are whatever is left from the width and height.
 	x2 := width
 	y2 := height
-	return col, row, colEnd, rowEnd, x2, y2
+	return col, row, xAbs, yAbs, colEnd, rowEnd, x2, y2
 }
 
 // getColWidth provides a function to get column width in pixels by given
@@ -627,15 +450,15 @@ func (f *File) getColWidth(sheet string, col int) int {
 func (f *File) GetColWidth(sheet, col string) (float64, error) {
 	colNum, err := ColumnNameToNumber(col)
 	if err != nil {
-		return defaultColWidth, err
+		return defaultColWidthPixels, err
 	}
-	ws, err := f.workSheetReader(sheet)
+	xlsx, err := f.workSheetReader(sheet)
 	if err != nil {
-		return defaultColWidth, err
+		return defaultColWidthPixels, err
 	}
-	if ws.Cols != nil {
+	if xlsx.Cols != nil {
 		var width float64
-		for _, v := range ws.Cols.Col {
+		for _, v := range xlsx.Cols.Col {
 			if v.Min <= colNum && colNum <= v.Max {
 				width = v.Width
 			}
@@ -645,7 +468,7 @@ func (f *File) GetColWidth(sheet, col string) (float64, error) {
 		}
 	}
 	// Optimisation for when the column widths haven't changed.
-	return defaultColWidth, err
+	return defaultColWidthPixels, err
 }
 
 // InsertCol provides a function to insert a new column before given column
@@ -676,12 +499,12 @@ func (f *File) RemoveCol(sheet, col string) error {
 		return err
 	}
 
-	ws, err := f.workSheetReader(sheet)
+	xlsx, err := f.workSheetReader(sheet)
 	if err != nil {
 		return err
 	}
-	for rowIdx := range ws.SheetData.Row {
-		rowData := &ws.SheetData.Row[rowIdx]
+	for rowIdx := range xlsx.SheetData.Row {
+		rowData := &xlsx.SheetData.Row[rowIdx]
 		for colIdx := range rowData.C {
 			colName, _, _ := SplitCellName(rowData.C[colIdx].R)
 			if colName == col {

+ 6 - 173
col_test.go

@@ -5,169 +5,8 @@ import (
 	"testing"
 
 	"github.com/stretchr/testify/assert"
-	"github.com/stretchr/testify/require"
 )
 
-func TestCols(t *testing.T) {
-	const sheet2 = "Sheet2"
-
-	f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
-	if !assert.NoError(t, err) {
-		t.FailNow()
-	}
-
-	cols, err := f.Cols(sheet2)
-	if !assert.NoError(t, err) {
-		t.FailNow()
-	}
-
-	var collectedRows [][]string
-	for cols.Next() {
-		rows, err := cols.Rows()
-		assert.NoError(t, err)
-		collectedRows = append(collectedRows, trimSliceSpace(rows))
-	}
-	if !assert.NoError(t, cols.Error()) {
-		t.FailNow()
-	}
-
-	returnedColumns, err := f.GetCols(sheet2)
-	assert.NoError(t, err)
-	for i := range returnedColumns {
-		returnedColumns[i] = trimSliceSpace(returnedColumns[i])
-	}
-	if !assert.Equal(t, collectedRows, returnedColumns) {
-		t.FailNow()
-	}
-
-	f = NewFile()
-	cells := []string{"C2", "C3", "C4"}
-	for _, cell := range cells {
-		assert.NoError(t, f.SetCellValue("Sheet1", cell, 1))
-	}
-	_, err = f.Rows("Sheet1")
-	assert.NoError(t, err)
-
-	f.Sheet["xl/worksheets/sheet1.xml"] = &xlsxWorksheet{
-		Dimension: &xlsxDimension{
-			Ref: "C2:C4",
-		},
-	}
-	_, err = f.Rows("Sheet1")
-	assert.NoError(t, err)
-}
-
-func TestColumnsIterator(t *testing.T) {
-	const (
-		sheet2         = "Sheet2"
-		expectedNumCol = 9
-	)
-
-	f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
-	require.NoError(t, err)
-
-	cols, err := f.Cols(sheet2)
-	require.NoError(t, err)
-
-	var colCount int
-	for cols.Next() {
-		colCount++
-		require.True(t, colCount <= expectedNumCol, "colCount is greater than expected")
-	}
-	assert.Equal(t, expectedNumCol, colCount)
-
-	f = NewFile()
-	cells := []string{"C2", "C3", "C4", "D2", "D3", "D4"}
-	for _, cell := range cells {
-		assert.NoError(t, f.SetCellValue("Sheet1", cell, 1))
-	}
-	cols, err = f.Cols("Sheet1")
-	require.NoError(t, err)
-
-	colCount = 0
-	for cols.Next() {
-		colCount++
-		require.True(t, colCount <= 4, "colCount is greater than expected")
-	}
-	assert.Equal(t, 4, colCount)
-}
-
-func TestColsError(t *testing.T) {
-	f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
-	if !assert.NoError(t, err) {
-		t.FailNow()
-	}
-	_, err = f.Cols("SheetN")
-	assert.EqualError(t, err, "sheet SheetN is not exist")
-}
-
-func TestGetColsError(t *testing.T) {
-	f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
-	if !assert.NoError(t, err) {
-		t.FailNow()
-	}
-	_, err = f.GetCols("SheetN")
-	assert.EqualError(t, err, "sheet SheetN is not exist")
-
-	f = NewFile()
-	delete(f.Sheet, "xl/worksheets/sheet1.xml")
-	f.XLSX["xl/worksheets/sheet1.xml"] = []byte(`<worksheet><sheetData><row r="A"><c r="2" t="str"><v>B</v></c></row></sheetData></worksheet>`)
-	f.checked = nil
-	_, err = f.GetCols("Sheet1")
-	assert.EqualError(t, err, `strconv.Atoi: parsing "A": invalid syntax`)
-
-	f = NewFile()
-	delete(f.Sheet, "xl/worksheets/sheet1.xml")
-	f.XLSX["xl/worksheets/sheet1.xml"] = []byte(`<worksheet><sheetData><row r="2"><c r="A" t="str"><v>B</v></c></row></sheetData></worksheet>`)
-	f.checked = nil
-	_, err = f.GetCols("Sheet1")
-	assert.EqualError(t, err, `cannot convert cell "A" to coordinates: invalid cell name "A"`)
-
-	f = NewFile()
-	cols, err := f.Cols("Sheet1")
-	assert.NoError(t, err)
-	cols.totalRow = 2
-	cols.totalCol = 2
-	cols.curCol = 1
-	cols.sheetXML = []byte(`<worksheet><sheetData><row r="1"><c r="A" t="str"><v>A</v></c></row></sheetData></worksheet>`)
-	_, err = cols.Rows()
-	assert.EqualError(t, err, `cannot convert cell "A" to coordinates: invalid cell name "A"`)
-}
-
-func TestColsRows(t *testing.T) {
-	f := NewFile()
-	f.NewSheet("Sheet1")
-
-	_, err := f.Cols("Sheet1")
-	assert.NoError(t, err)
-
-	assert.NoError(t, f.SetCellValue("Sheet1", "A1", 1))
-	f.Sheet["xl/worksheets/sheet1.xml"] = &xlsxWorksheet{
-		Dimension: &xlsxDimension{
-			Ref: "A1:A1",
-		},
-	}
-
-	f = NewFile()
-	f.XLSX["xl/worksheets/sheet1.xml"] = nil
-	_, err = f.Cols("Sheet1")
-	if !assert.NoError(t, err) {
-		t.FailNow()
-	}
-	f = NewFile()
-	cols, err := f.Cols("Sheet1")
-	if !assert.NoError(t, err) {
-		t.FailNow()
-	}
-	_, err = cols.Rows()
-	assert.NoError(t, err)
-	cols.stashCol, cols.curCol = 0, 1
-	// Test if token is nil
-	cols.sheetXML = nil
-	_, err = cols.Rows()
-	assert.NoError(t, err)
-}
-
 func TestColumnVisibility(t *testing.T) {
 	t.Run("TestBook1", func(t *testing.T) {
 		f, err := prepareTestBook1()
@@ -246,12 +85,10 @@ func TestOutlineLevel(t *testing.T) {
 	assert.EqualError(t, err, "sheet Shee2 is not exist")
 
 	assert.NoError(t, f.SetColWidth("Sheet2", "A", "D", 13))
-	assert.EqualError(t, f.SetColWidth("Sheet2", "A", "D", MaxColumnWidth+1), ErrColumnWidth.Error())
-
 	assert.NoError(t, f.SetColOutlineLevel("Sheet2", "B", 2))
 	assert.NoError(t, f.SetRowOutlineLevel("Sheet1", 2, 7))
-	assert.EqualError(t, f.SetColOutlineLevel("Sheet1", "D", 8), ErrOutlineLevel.Error())
-	assert.EqualError(t, f.SetRowOutlineLevel("Sheet1", 2, 8), ErrOutlineLevel.Error())
+	assert.EqualError(t, f.SetColOutlineLevel("Sheet1", "D", 8), "invalid outline level")
+	assert.EqualError(t, f.SetRowOutlineLevel("Sheet1", 2, 8), "invalid outline level")
 	// Test set row outline level on not exists worksheet.
 	assert.EqualError(t, f.SetRowOutlineLevel("SheetN", 1, 4), "sheet SheetN is not exist")
 	// Test get row outline level on not exists worksheet.
@@ -310,12 +147,12 @@ func TestColWidth(t *testing.T) {
 	assert.Equal(t, float64(12), width)
 	assert.NoError(t, err)
 	width, err = f.GetColWidth("Sheet1", "C")
-	assert.Equal(t, defaultColWidth, width)
+	assert.Equal(t, float64(64), width)
 	assert.NoError(t, err)
 
 	// Test set and get column width with illegal cell coordinates.
 	width, err = f.GetColWidth("Sheet1", "*")
-	assert.Equal(t, defaultColWidth, width)
+	assert.Equal(t, float64(64), width)
 	assert.EqualError(t, err, `invalid column name "*"`)
 	assert.EqualError(t, f.SetColWidth("Sheet1", "*", "B", 1), `invalid column name "*"`)
 	assert.EqualError(t, f.SetColWidth("Sheet1", "A", "*", 1), `invalid column name "*"`)
@@ -333,7 +170,7 @@ func TestColWidth(t *testing.T) {
 
 func TestInsertCol(t *testing.T) {
 	f := NewFile()
-	sheet1 := f.GetSheetName(0)
+	sheet1 := f.GetSheetName(1)
 
 	fillCells(f, sheet1, 10, 10)
 
@@ -351,7 +188,7 @@ func TestInsertCol(t *testing.T) {
 
 func TestRemoveCol(t *testing.T) {
 	f := NewFile()
-	sheet1 := f.GetSheetName(0)
+	sheet1 := f.GetSheetName(1)
 
 	fillCells(f, sheet1, 10, 15)
 
@@ -372,7 +209,3 @@ func TestRemoveCol(t *testing.T) {
 
 	assert.NoError(t, f.SaveAs(filepath.Join("test", "TestRemoveCol.xlsx")))
 }
-
-func TestConvertColWidthToPixels(t *testing.T) {
-	assert.Equal(t, -11.0, convertColWidthToPixels(-1))
-}

+ 20 - 30
comment.go

@@ -1,13 +1,11 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
+// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
 // this source code is governed by a BSD-style license that can be found in
 // the LICENSE file.
 //
 // Package excelize providing a set of functions that allow you to write to
-// and read from XLSX / XLSM / XLTM files. Supports reading and writing
-// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
-// complex components by high compatibility, and provided streaming API for
-// generating or reading data from a worksheet with huge amounts of data. This
-// library needs Go version 1.15 or later.
+// and read from XLSX files. Support reads and writes XLSX file generated by
+// Microsoft Excel™ 2007 and later. Support save file without losing original
+// charts of XLSX. This library needs Go version 1.10 or later.
 
 package excelize
 
@@ -39,19 +37,12 @@ func parseFormatCommentsSet(formatSet string) (*formatComment, error) {
 func (f *File) GetComments() (comments map[string][]Comment) {
 	comments = map[string][]Comment{}
 	for n, path := range f.sheetMap {
-		target := f.getSheetComments(filepath.Base(path))
-		if target == "" {
-			continue
-		}
-		if !strings.HasPrefix(target, "/") {
-			target = "xl" + strings.TrimPrefix(target, "..")
-		}
-		if d := f.commentsReader(strings.TrimPrefix(target, "/")); d != nil {
+		if d := f.commentsReader("xl" + strings.TrimPrefix(f.getSheetComments(filepath.Base(path)), "..")); d != nil {
 			sheetComments := []Comment{}
 			for _, comment := range d.CommentList.Comment {
 				sheetComment := Comment{}
-				if comment.AuthorID < len(d.Authors.Author) {
-					sheetComment.Author = d.Authors.Author[comment.AuthorID]
+				if comment.AuthorID < len(d.Authors) {
+					sheetComment.Author = d.Authors[comment.AuthorID].Author
 				}
 				sheetComment.Ref = comment.Ref
 				sheetComment.AuthorID = comment.AuthorID
@@ -98,7 +89,7 @@ func (f *File) AddComment(sheet, cell, format string) error {
 		return err
 	}
 	// Read sheet data.
-	ws, err := f.workSheetReader(sheet)
+	xlsx, err := f.workSheetReader(sheet)
 	if err != nil {
 		return err
 	}
@@ -106,9 +97,9 @@ func (f *File) AddComment(sheet, cell, format string) error {
 	drawingVML := "xl/drawings/vmlDrawing" + strconv.Itoa(commentID) + ".vml"
 	sheetRelationshipsComments := "../comments" + strconv.Itoa(commentID) + ".xml"
 	sheetRelationshipsDrawingVML := "../drawings/vmlDrawing" + strconv.Itoa(commentID) + ".vml"
-	if ws.LegacyDrawing != nil {
+	if xlsx.LegacyDrawing != nil {
 		// The worksheet already has a comments relationships, use the relationships drawing ../drawings/vmlDrawing%d.vml.
-		sheetRelationshipsDrawingVML = f.getSheetRelationshipsTargetByID(sheet, ws.LegacyDrawing.RID)
+		sheetRelationshipsDrawingVML = f.getSheetRelationshipsTargetByID(sheet, xlsx.LegacyDrawing.RID)
 		commentID, _ = strconv.Atoi(strings.TrimSuffix(strings.TrimPrefix(sheetRelationshipsDrawingVML, "../drawings/vmlDrawing"), ".vml"))
 		drawingVML = strings.Replace(sheetRelationshipsDrawingVML, "..", "xl", -1)
 	} else {
@@ -116,7 +107,6 @@ func (f *File) AddComment(sheet, cell, format string) error {
 		sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(f.sheetMap[trimSheetName(sheet)], "xl/worksheets/") + ".rels"
 		rID := f.addRels(sheetRels, SourceRelationshipDrawingVML, sheetRelationshipsDrawingVML, "")
 		f.addRels(sheetRels, SourceRelationshipComments, sheetRelationshipsComments, "")
-		f.addSheetNameSpace(sheet, SourceRelationship)
 		f.addSheetLegacyDrawing(sheet, rID)
 	}
 	commentsXML := "xl/comments" + strconv.Itoa(commentID) + ".xml"
@@ -171,7 +161,7 @@ func (f *File) addDrawingVML(commentID int, drawingVML, cell string, lineCount,
 				},
 				VPath: &vPath{
 					Gradientshapeok: "t",
-					Connecttype:     "rect",
+					Connecttype:     "miter",
 				},
 			},
 		}
@@ -250,24 +240,24 @@ func (f *File) addComment(commentsXML, cell string, formatSet *formatComment) {
 		t = t[0:32512]
 	}
 	comments := f.commentsReader(commentsXML)
-	authorID := 0
 	if comments == nil {
-		comments = &xlsxComments{Authors: xlsxAuthor{Author: []string{formatSet.Author}}}
-	}
-	if inStrSlice(comments.Authors.Author, formatSet.Author) == -1 {
-		comments.Authors.Author = append(comments.Authors.Author, formatSet.Author)
-		authorID = len(comments.Authors.Author) - 1
+		comments = &xlsxComments{
+			Authors: []xlsxAuthor{
+				{
+					Author: formatSet.Author,
+				},
+			},
+		}
 	}
 	defaultFont := f.GetDefaultFont()
-	bold := ""
 	cmt := xlsxComment{
 		Ref:      cell,
-		AuthorID: authorID,
+		AuthorID: 0,
 		Text: xlsxText{
 			R: []xlsxR{
 				{
 					RPr: &xlsxRPr{
-						B:  &bold,
+						B:  " ",
 						Sz: &attrValFloat{Val: float64Ptr(9)},
 						Color: &xlsxColor{
 							Indexed: 81,

+ 2 - 3
comment_test.go

@@ -1,11 +1,11 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
+// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
 // this source code is governed by a BSD-style license that can be found in
 // the LICENSE file.
 //
 // Package excelize providing a set of functions that allow you to write to
 // and read from XLSX files. Support reads and writes XLSX file generated by
 // Microsoft Excel™ 2007 and later. Support save file without losing original
-// charts of XLSX. This library needs Go version 1.15 or later.
+// charts of XLSX. This library needs Go version 1.10 or later.
 
 package excelize
 
@@ -40,7 +40,6 @@ func TestAddComments(t *testing.T) {
 	comments := f.GetComments()
 	assert.EqualValues(t, 2, len(comments["Sheet1"]))
 	assert.EqualValues(t, 1, len(comments["Sheet2"]))
-	assert.EqualValues(t, len(NewFile().GetComments()), 0)
 }
 
 func TestDecodeVMLDrawingReader(t *testing.T) {

+ 0 - 616
crypt.go

@@ -1,616 +0,0 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
-// this source code is governed by a BSD-style license that can be found in
-// the LICENSE file.
-//
-// Package excelize providing a set of functions that allow you to write to
-// and read from XLSX files. Support reads and writes XLSX file generated by
-// Microsoft Excel™ 2007 and later. Support save file without losing original
-// charts of XLSX. This library needs Go version 1.15 or later.
-
-package excelize
-
-import (
-	"bytes"
-	"crypto/aes"
-	"crypto/cipher"
-	"crypto/hmac"
-	"crypto/md5"
-	"crypto/sha1"
-	"crypto/sha256"
-	"crypto/sha512"
-	"encoding/base64"
-	"encoding/binary"
-	"encoding/xml"
-	"errors"
-	"hash"
-	"math/rand"
-	"reflect"
-	"strings"
-
-	"github.com/richardlehane/mscfb"
-	"golang.org/x/crypto/md4"
-	"golang.org/x/crypto/ripemd160"
-	"golang.org/x/text/encoding/unicode"
-)
-
-var (
-	blockKey                   = []byte{0x14, 0x6e, 0x0b, 0xe7, 0xab, 0xac, 0xd0, 0xd6} // Block keys used for encryption
-	blockKeyHmacKey            = []byte{0x5f, 0xb2, 0xad, 0x01, 0x0c, 0xb9, 0xe1, 0xf6}
-	blockKeyHmacValue          = []byte{0xa0, 0x67, 0x7f, 0x02, 0xb2, 0x2c, 0x84, 0x33}
-	blockKeyVerifierHashInput  = []byte{0xfe, 0xa7, 0xd2, 0x76, 0x3b, 0x4b, 0x9e, 0x79}
-	blockKeyVerifierHashValue  = []byte{0xd7, 0xaa, 0x0f, 0x6d, 0x30, 0x61, 0x34, 0x4e}
-	packageOffset              = 8 // First 8 bytes are the size of the stream
-	packageEncryptionChunkSize = 4096
-	iterCount                  = 50000
-	oleIdentifier              = []byte{
-		0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1,
-	}
-)
-
-// Encryption specifies the encryption structure, streams, and storages are
-// required when encrypting ECMA-376 documents.
-type Encryption struct {
-	XMLName       xml.Name      `xml:"encryption"`
-	KeyData       KeyData       `xml:"keyData"`
-	DataIntegrity DataIntegrity `xml:"dataIntegrity"`
-	KeyEncryptors KeyEncryptors `xml:"keyEncryptors"`
-}
-
-// KeyData specifies the cryptographic attributes used to encrypt the data.
-type KeyData struct {
-	SaltSize        int    `xml:"saltSize,attr"`
-	BlockSize       int    `xml:"blockSize,attr"`
-	KeyBits         int    `xml:"keyBits,attr"`
-	HashSize        int    `xml:"hashSize,attr"`
-	CipherAlgorithm string `xml:"cipherAlgorithm,attr"`
-	CipherChaining  string `xml:"cipherChaining,attr"`
-	HashAlgorithm   string `xml:"hashAlgorithm,attr"`
-	SaltValue       string `xml:"saltValue,attr"`
-}
-
-// DataIntegrity specifies the encrypted copies of the salt and hash values
-// used to help ensure that the integrity of the encrypted data has not been
-// compromised.
-type DataIntegrity struct {
-	EncryptedHmacKey   string `xml:"encryptedHmacKey,attr"`
-	EncryptedHmacValue string `xml:"encryptedHmacValue,attr"`
-}
-
-// KeyEncryptors specifies the key encryptors used to encrypt the data.
-type KeyEncryptors struct {
-	KeyEncryptor []KeyEncryptor `xml:"keyEncryptor"`
-}
-
-// KeyEncryptor specifies that the schema used by this encryptor is the schema
-// specified for password-based encryptors.
-type KeyEncryptor struct {
-	XMLName      xml.Name     `xml:"keyEncryptor"`
-	URI          string       `xml:"uri,attr"`
-	EncryptedKey EncryptedKey `xml:"encryptedKey"`
-}
-
-// EncryptedKey used to generate the encrypting key.
-type EncryptedKey struct {
-	XMLName                    xml.Name `xml:"http://schemas.microsoft.com/office/2006/keyEncryptor/password encryptedKey"`
-	SpinCount                  int      `xml:"spinCount,attr"`
-	EncryptedVerifierHashInput string   `xml:"encryptedVerifierHashInput,attr"`
-	EncryptedVerifierHashValue string   `xml:"encryptedVerifierHashValue,attr"`
-	EncryptedKeyValue          string   `xml:"encryptedKeyValue,attr"`
-	KeyData
-}
-
-// StandardEncryptionHeader structure is used by ECMA-376 document encryption
-// [ECMA-376] and Office binary document RC4 CryptoAPI encryption, to specify
-// encryption properties for an encrypted stream.
-type StandardEncryptionHeader struct {
-	Flags        uint32
-	SizeExtra    uint32
-	AlgID        uint32
-	AlgIDHash    uint32
-	KeySize      uint32
-	ProviderType uint32
-	Reserved1    uint32
-	Reserved2    uint32
-	CspName      string
-}
-
-// StandardEncryptionVerifier structure is used by Office Binary Document RC4
-// CryptoAPI Encryption and ECMA-376 Document Encryption. Every usage of this
-// structure MUST specify the hashing algorithm and encryption algorithm used
-// in the EncryptionVerifier structure.
-type StandardEncryptionVerifier struct {
-	SaltSize              uint32
-	Salt                  []byte
-	EncryptedVerifier     []byte
-	VerifierHashSize      uint32
-	EncryptedVerifierHash []byte
-}
-
-// Decrypt API decrypt the CFB file format with ECMA-376 agile encryption and
-// standard encryption. Support cryptographic algorithm: MD4, MD5, RIPEMD-160,
-// SHA1, SHA256, SHA384 and SHA512 currently.
-func Decrypt(raw []byte, opt *Options) (packageBuf []byte, err error) {
-	doc, err := mscfb.New(bytes.NewReader(raw))
-	if err != nil {
-		return
-	}
-	encryptionInfoBuf, encryptedPackageBuf := extractPart(doc)
-	mechanism, err := encryptionMechanism(encryptionInfoBuf)
-	if err != nil || mechanism == "extensible" {
-		return
-	}
-	switch mechanism {
-	case "agile":
-		return agileDecrypt(encryptionInfoBuf, encryptedPackageBuf, opt)
-	case "standard":
-		return standardDecrypt(encryptionInfoBuf, encryptedPackageBuf, opt)
-	default:
-		err = errors.New("unsupport encryption mechanism")
-	}
-	return
-}
-
-// Encrypt API encrypt data with the password.
-func Encrypt(raw []byte, opt *Options) (packageBuf []byte, err error) {
-	// Generate a random key to use to encrypt the document. Excel uses 32 bytes. We'll use the password to encrypt this key.
-	packageKey, _ := randomBytes(32)
-	keyDataSaltValue, _ := randomBytes(16)
-	keyEncryptors, _ := randomBytes(16)
-	encryptionInfo := Encryption{
-		KeyData: KeyData{
-			BlockSize:       16,
-			KeyBits:         len(packageKey) * 8,
-			HashSize:        64,
-			CipherAlgorithm: "AES",
-			CipherChaining:  "ChainingModeCBC",
-			HashAlgorithm:   "SHA512",
-			SaltValue:       base64.StdEncoding.EncodeToString(keyDataSaltValue),
-		},
-		KeyEncryptors: KeyEncryptors{KeyEncryptor: []KeyEncryptor{{
-			EncryptedKey: EncryptedKey{SpinCount: 100000, KeyData: KeyData{
-				CipherAlgorithm: "AES",
-				CipherChaining:  "ChainingModeCBC",
-				HashAlgorithm:   "SHA512",
-				HashSize:        64,
-				BlockSize:       16,
-				KeyBits:         256,
-				SaltValue:       base64.StdEncoding.EncodeToString(keyEncryptors)},
-			}}},
-		},
-	}
-
-	// Package Encryption
-
-	// Encrypt package using the package key.
-	encryptedPackage, err := cryptPackage(true, packageKey, raw, encryptionInfo)
-	if err != nil {
-		return
-	}
-
-	// Data Integrity
-
-	// Create the data integrity fields used by clients for integrity checks.
-	// Generate a random array of bytes to use in HMAC. The docs say to use the same length as the key salt, but Excel seems to use 64.
-	hmacKey, _ := randomBytes(64)
-	if err != nil {
-		return
-	}
-	// Create an initialization vector using the package encryption info and the appropriate block key.
-	hmacKeyIV, err := createIV(blockKeyHmacKey, encryptionInfo)
-	if err != nil {
-		return
-	}
-	// Use the package key and the IV to encrypt the HMAC key.
-	encryptedHmacKey, _ := crypt(true, encryptionInfo.KeyData.CipherAlgorithm, encryptionInfo.KeyData.CipherChaining, packageKey, hmacKeyIV, hmacKey)
-	// Create the HMAC.
-	h := hmac.New(sha512.New, append(hmacKey, encryptedPackage...))
-	for _, buf := range [][]byte{hmacKey, encryptedPackage} {
-		_, _ = h.Write(buf)
-	}
-	hmacValue := h.Sum(nil)
-	// Generate an initialization vector for encrypting the resulting HMAC value.
-	hmacValueIV, err := createIV(blockKeyHmacValue, encryptionInfo)
-	if err != nil {
-		return
-	}
-	// Encrypt the value.
-	encryptedHmacValue, _ := crypt(true, encryptionInfo.KeyData.CipherAlgorithm, encryptionInfo.KeyData.CipherChaining, packageKey, hmacValueIV, hmacValue)
-	// Put the encrypted key and value on the encryption info.
-	encryptionInfo.DataIntegrity.EncryptedHmacKey = base64.StdEncoding.EncodeToString(encryptedHmacKey)
-	encryptionInfo.DataIntegrity.EncryptedHmacValue = base64.StdEncoding.EncodeToString(encryptedHmacValue)
-
-	// Key Encryption
-
-	// Convert the password to an encryption key.
-	key, err := convertPasswdToKey(opt.Password, blockKey, encryptionInfo)
-	if err != nil {
-		return
-	}
-	// Encrypt the package key with the encryption key.
-	encryptedKeyValue, _ := crypt(true, encryptionInfo.KeyEncryptors.KeyEncryptor[0].EncryptedKey.CipherAlgorithm, encryptionInfo.KeyEncryptors.KeyEncryptor[0].EncryptedKey.CipherChaining, key, keyEncryptors, packageKey)
-	encryptionInfo.KeyEncryptors.KeyEncryptor[0].EncryptedKey.EncryptedKeyValue = base64.StdEncoding.EncodeToString(encryptedKeyValue)
-
-	// Verifier hash
-
-	// Create a random byte array for hashing.
-	verifierHashInput, _ := randomBytes(16)
-	// Create an encryption key from the password for the input.
-	verifierHashInputKey, err := convertPasswdToKey(opt.Password, blockKeyVerifierHashInput, encryptionInfo)
-	if err != nil {
-		return
-	}
-	// Use the key to encrypt the verifier input.
-	encryptedVerifierHashInput, err := crypt(true, encryptionInfo.KeyData.CipherAlgorithm, encryptionInfo.KeyData.CipherChaining, verifierHashInputKey, keyEncryptors, verifierHashInput)
-	if err != nil {
-		return
-	}
-	encryptionInfo.KeyEncryptors.KeyEncryptor[0].EncryptedKey.EncryptedVerifierHashInput = base64.StdEncoding.EncodeToString(encryptedVerifierHashInput)
-	// Create a hash of the input.
-	verifierHashValue := hashing(encryptionInfo.KeyData.HashAlgorithm, verifierHashInput)
-	// Create an encryption key from the password for the hash.
-	verifierHashValueKey, err := convertPasswdToKey(opt.Password, blockKeyVerifierHashValue, encryptionInfo)
-	if err != nil {
-		return
-	}
-	// Use the key to encrypt the hash value.
-	encryptedVerifierHashValue, err := crypt(true, encryptionInfo.KeyData.CipherAlgorithm, encryptionInfo.KeyData.CipherChaining, verifierHashValueKey, keyEncryptors, verifierHashValue)
-	if err != nil {
-		return
-	}
-	encryptionInfo.KeyEncryptors.KeyEncryptor[0].EncryptedKey.EncryptedVerifierHashValue = base64.StdEncoding.EncodeToString(encryptedVerifierHashValue)
-	// Marshal the encryption info buffer.
-	encryptionInfoBuffer, err := xml.Marshal(encryptionInfo)
-	if err != nil {
-		return
-	}
-	// TODO: Create a new CFB.
-	_, _ = encryptedPackage, encryptionInfoBuffer
-	err = errors.New("not support encryption currently")
-	return
-}
-
-// extractPart extract data from storage by specified part name.
-func extractPart(doc *mscfb.Reader) (encryptionInfoBuf, encryptedPackageBuf []byte) {
-	for entry, err := doc.Next(); err == nil; entry, err = doc.Next() {
-		switch entry.Name {
-		case "EncryptionInfo":
-			buf := make([]byte, entry.Size)
-			i, _ := doc.Read(buf)
-			if i > 0 {
-				encryptionInfoBuf = buf
-			}
-		case "EncryptedPackage":
-			buf := make([]byte, entry.Size)
-			i, _ := doc.Read(buf)
-			if i > 0 {
-				encryptedPackageBuf = buf
-			}
-		}
-	}
-	return
-}
-
-// encryptionMechanism parse password-protected documents created mechanism.
-func encryptionMechanism(buffer []byte) (mechanism string, err error) {
-	if len(buffer) < 4 {
-		err = errors.New("unknown encryption mechanism")
-		return
-	}
-	versionMajor, versionMinor := binary.LittleEndian.Uint16(buffer[0:2]), binary.LittleEndian.Uint16(buffer[2:4])
-	if versionMajor == 4 && versionMinor == 4 {
-		mechanism = "agile"
-		return
-	} else if (2 <= versionMajor && versionMajor <= 4) && versionMinor == 2 {
-		mechanism = "standard"
-		return
-	} else if (versionMajor == 3 || versionMajor == 4) && versionMinor == 3 {
-		mechanism = "extensible"
-	}
-	err = errors.New("unsupport encryption mechanism")
-	return
-}
-
-// ECMA-376 Standard Encryption
-
-// standardDecrypt decrypt the CFB file format with ECMA-376 standard encryption.
-func standardDecrypt(encryptionInfoBuf, encryptedPackageBuf []byte, opt *Options) ([]byte, error) {
-	encryptionHeaderSize := binary.LittleEndian.Uint32(encryptionInfoBuf[8:12])
-	block := encryptionInfoBuf[12 : 12+encryptionHeaderSize]
-	header := StandardEncryptionHeader{
-		Flags:        binary.LittleEndian.Uint32(block[:4]),
-		SizeExtra:    binary.LittleEndian.Uint32(block[4:8]),
-		AlgID:        binary.LittleEndian.Uint32(block[8:12]),
-		AlgIDHash:    binary.LittleEndian.Uint32(block[12:16]),
-		KeySize:      binary.LittleEndian.Uint32(block[16:20]),
-		ProviderType: binary.LittleEndian.Uint32(block[20:24]),
-		Reserved1:    binary.LittleEndian.Uint32(block[24:28]),
-		Reserved2:    binary.LittleEndian.Uint32(block[28:32]),
-		CspName:      string(block[32:]),
-	}
-	block = encryptionInfoBuf[12+encryptionHeaderSize:]
-	algIDMap := map[uint32]string{
-		0x0000660E: "AES-128",
-		0x0000660F: "AES-192",
-		0x00006610: "AES-256",
-	}
-	algorithm := "AES"
-	_, ok := algIDMap[header.AlgID]
-	if !ok {
-		algorithm = "RC4"
-	}
-	verifier := standardEncryptionVerifier(algorithm, block)
-	secretKey, err := standardConvertPasswdToKey(header, verifier, opt)
-	if err != nil {
-		return nil, err
-	}
-	// decrypted data
-	x := encryptedPackageBuf[8:]
-	blob, err := aes.NewCipher(secretKey)
-	if err != nil {
-		return nil, err
-	}
-	decrypted := make([]byte, len(x))
-	size := 16
-	for bs, be := 0, size; bs < len(x); bs, be = bs+size, be+size {
-		blob.Decrypt(decrypted[bs:be], x[bs:be])
-	}
-	return decrypted, err
-}
-
-// standardEncryptionVerifier extract ECMA-376 standard encryption verifier.
-func standardEncryptionVerifier(algorithm string, blob []byte) StandardEncryptionVerifier {
-	verifier := StandardEncryptionVerifier{
-		SaltSize:          binary.LittleEndian.Uint32(blob[:4]),
-		Salt:              blob[4:20],
-		EncryptedVerifier: blob[20:36],
-		VerifierHashSize:  binary.LittleEndian.Uint32(blob[36:40]),
-	}
-	if algorithm == "RC4" {
-		verifier.EncryptedVerifierHash = blob[40:60]
-	} else if algorithm == "AES" {
-		verifier.EncryptedVerifierHash = blob[40:72]
-	}
-	return verifier
-}
-
-// standardConvertPasswdToKey generate intermediate key from given password.
-func standardConvertPasswdToKey(header StandardEncryptionHeader, verifier StandardEncryptionVerifier, opt *Options) ([]byte, error) {
-	encoder := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewEncoder()
-	passwordBuffer, err := encoder.Bytes([]byte(opt.Password))
-	if err != nil {
-		return nil, err
-	}
-	key := hashing("sha1", verifier.Salt, passwordBuffer)
-	for i := 0; i < iterCount; i++ {
-		iterator := createUInt32LEBuffer(i, 4)
-		key = hashing("sha1", iterator, key)
-	}
-	var block int
-	hfinal := hashing("sha1", key, createUInt32LEBuffer(block, 4))
-	cbRequiredKeyLength := int(header.KeySize) / 8
-	cbHash := sha1.Size
-	buf1 := bytes.Repeat([]byte{0x36}, 64)
-	buf1 = append(standardXORBytes(hfinal, buf1[:cbHash]), buf1[cbHash:]...)
-	x1 := hashing("sha1", buf1)
-	buf2 := bytes.Repeat([]byte{0x5c}, 64)
-	buf2 = append(standardXORBytes(hfinal, buf2[:cbHash]), buf2[cbHash:]...)
-	x2 := hashing("sha1", buf2)
-	x3 := append(x1, x2...)
-	keyDerived := x3[:cbRequiredKeyLength]
-	return keyDerived, err
-}
-
-// standardXORBytes perform XOR operations for two bytes slice.
-func standardXORBytes(a, b []byte) []byte {
-	r := make([][2]byte, len(a))
-	for i, e := range a {
-		r[i] = [2]byte{e, b[i]}
-	}
-	buf := make([]byte, len(a))
-	for p, q := range r {
-		buf[p] = q[0] ^ q[1]
-	}
-	return buf
-}
-
-// ECMA-376 Agile Encryption
-
-// agileDecrypt decrypt the CFB file format with ECMA-376 agile encryption.
-// Support cryptographic algorithm: MD4, MD5, RIPEMD-160, SHA1, SHA256, SHA384 and SHA512.
-func agileDecrypt(encryptionInfoBuf, encryptedPackageBuf []byte, opt *Options) (packageBuf []byte, err error) {
-	var encryptionInfo Encryption
-	if encryptionInfo, err = parseEncryptionInfo(encryptionInfoBuf[8:]); err != nil {
-		return
-	}
-	// Convert the password into an encryption key.
-	key, err := convertPasswdToKey(opt.Password, blockKey, encryptionInfo)
-	if err != nil {
-		return
-	}
-	// Use the key to decrypt the package key.
-	encryptedKey := encryptionInfo.KeyEncryptors.KeyEncryptor[0].EncryptedKey
-	saltValue, err := base64.StdEncoding.DecodeString(encryptedKey.SaltValue)
-	if err != nil {
-		return
-	}
-	encryptedKeyValue, err := base64.StdEncoding.DecodeString(encryptedKey.EncryptedKeyValue)
-	if err != nil {
-		return
-	}
-	packageKey, _ := crypt(false, encryptedKey.CipherAlgorithm, encryptedKey.CipherChaining, key, saltValue, encryptedKeyValue)
-	// Use the package key to decrypt the package.
-	return cryptPackage(false, packageKey, encryptedPackageBuf, encryptionInfo)
-}
-
-// convertPasswdToKey convert the password into an encryption key.
-func convertPasswdToKey(passwd string, blockKey []byte, encryption Encryption) (key []byte, err error) {
-	var b bytes.Buffer
-	saltValue, err := base64.StdEncoding.DecodeString(encryption.KeyEncryptors.KeyEncryptor[0].EncryptedKey.SaltValue)
-	if err != nil {
-		return
-	}
-	b.Write(saltValue)
-	encoder := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewEncoder()
-	passwordBuffer, err := encoder.Bytes([]byte(passwd))
-	if err != nil {
-		return
-	}
-	b.Write(passwordBuffer)
-	// Generate the initial hash.
-	key = hashing(encryption.KeyData.HashAlgorithm, b.Bytes())
-	// Now regenerate until spin count.
-	for i := 0; i < encryption.KeyEncryptors.KeyEncryptor[0].EncryptedKey.SpinCount; i++ {
-		iterator := createUInt32LEBuffer(i, 4)
-		key = hashing(encryption.KeyData.HashAlgorithm, iterator, key)
-	}
-	// Now generate the final hash.
-	key = hashing(encryption.KeyData.HashAlgorithm, key, blockKey)
-	// Truncate or pad as needed to get to length of keyBits.
-	keyBytes := encryption.KeyEncryptors.KeyEncryptor[0].EncryptedKey.KeyBits / 8
-	if len(key) < keyBytes {
-		tmp := make([]byte, 0x36)
-		key = append(key, tmp...)
-		key = tmp
-	} else if len(key) > keyBytes {
-		key = key[:keyBytes]
-	}
-	return
-}
-
-// hashing data by specified hash algorithm.
-func hashing(hashAlgorithm string, buffer ...[]byte) (key []byte) {
-	var hashMap = map[string]hash.Hash{
-		"md4":        md4.New(),
-		"md5":        md5.New(),
-		"ripemd-160": ripemd160.New(),
-		"sha1":       sha1.New(),
-		"sha256":     sha256.New(),
-		"sha384":     sha512.New384(),
-		"sha512":     sha512.New(),
-	}
-	handler, ok := hashMap[strings.ToLower(hashAlgorithm)]
-	if !ok {
-		return key
-	}
-	for _, buf := range buffer {
-		_, _ = handler.Write(buf)
-	}
-	key = handler.Sum(nil)
-	return key
-}
-
-// createUInt32LEBuffer create buffer with little endian 32-bit unsigned
-// integer.
-func createUInt32LEBuffer(value int, bufferSize int) []byte {
-	buf := make([]byte, bufferSize)
-	binary.LittleEndian.PutUint32(buf, uint32(value))
-	return buf
-}
-
-// parseEncryptionInfo parse the encryption info XML into an object.
-func parseEncryptionInfo(encryptionInfo []byte) (encryption Encryption, err error) {
-	err = xml.Unmarshal(encryptionInfo, &encryption)
-	return
-}
-
-// crypt encrypt / decrypt input by given cipher algorithm, cipher chaining,
-// key and initialization vector.
-func crypt(encrypt bool, cipherAlgorithm, cipherChaining string, key, iv, input []byte) (packageKey []byte, err error) {
-	block, err := aes.NewCipher(key)
-	if err != nil {
-		return input, err
-	}
-	var stream cipher.BlockMode
-	if encrypt {
-		stream = cipher.NewCBCEncrypter(block, iv)
-	} else {
-		stream = cipher.NewCBCDecrypter(block, iv)
-	}
-	stream.CryptBlocks(input, input)
-	return input, nil
-}
-
-// cryptPackage encrypt / decrypt package by given packageKey and encryption
-// info.
-func cryptPackage(encrypt bool, packageKey, input []byte, encryption Encryption) (outputChunks []byte, err error) {
-	encryptedKey := encryption.KeyData
-	var offset = packageOffset
-	if encrypt {
-		offset = 0
-	}
-	var i, start, end int
-	var iv, outputChunk []byte
-	for end < len(input) {
-		start = end
-		end = start + packageEncryptionChunkSize
-
-		if end > len(input) {
-			end = len(input)
-		}
-		// Grab the next chunk
-		var inputChunk []byte
-		if (end + offset) < len(input) {
-			inputChunk = input[start+offset : end+offset]
-		} else {
-			inputChunk = input[start+offset : end]
-		}
-
-		// Pad the chunk if it is not an integer multiple of the block size
-		remainder := len(inputChunk) % encryptedKey.BlockSize
-		if remainder != 0 {
-			inputChunk = append(inputChunk, make([]byte, encryptedKey.BlockSize-remainder)...)
-		}
-		// Create the initialization vector
-		iv, err = createIV(i, encryption)
-		if err != nil {
-			return
-		}
-		// Encrypt/decrypt the chunk and add it to the array
-		outputChunk, err = crypt(encrypt, encryptedKey.CipherAlgorithm, encryptedKey.CipherChaining, packageKey, iv, inputChunk)
-		if err != nil {
-			return
-		}
-		outputChunks = append(outputChunks, outputChunk...)
-		i++
-	}
-	if encrypt {
-		outputChunks = append(createUInt32LEBuffer(len(input), 8), outputChunks...)
-	}
-	return
-}
-
-// createIV create an initialization vector (IV).
-func createIV(blockKey interface{}, encryption Encryption) ([]byte, error) {
-	encryptedKey := encryption.KeyData
-	// Create the block key from the current index
-	var blockKeyBuf []byte
-	if reflect.TypeOf(blockKey).Kind() == reflect.Int {
-		blockKeyBuf = createUInt32LEBuffer(blockKey.(int), 4)
-	} else {
-		blockKeyBuf = blockKey.([]byte)
-	}
-	saltValue, err := base64.StdEncoding.DecodeString(encryptedKey.SaltValue)
-	if err != nil {
-		return nil, err
-	}
-	// Create the initialization vector by hashing the salt with the block key.
-	// Truncate or pad as needed to meet the block size.
-	iv := hashing(encryptedKey.HashAlgorithm, append(saltValue, blockKeyBuf...))
-	if len(iv) < encryptedKey.BlockSize {
-		tmp := make([]byte, 0x36)
-		iv = append(iv, tmp...)
-		iv = tmp
-	} else if len(iv) > encryptedKey.BlockSize {
-		iv = iv[0:encryptedKey.BlockSize]
-	}
-	return iv, nil
-}
-
-// randomBytes returns securely generated random bytes. It will return an error if the system's
-// secure random number generator fails to function correctly, in which case the caller should not
-// continue.
-func randomBytes(n int) ([]byte, error) {
-	b := make([]byte, n)
-	_, err := rand.Read(b)
-	return b, err
-}

+ 0 - 35
crypt_test.go

@@ -1,35 +0,0 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
-// this source code is governed by a BSD-style license that can be found in
-// the LICENSE file.
-//
-// Package excelize providing a set of functions that allow you to write to
-// and read from XLSX files. Support reads and writes XLSX file generated by
-// Microsoft Excel™ 2007 and later. Support save file without losing original
-// charts of XLSX. This library needs Go version 1.15 or later.
-
-package excelize
-
-import (
-	"path/filepath"
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-)
-
-func TestEncrypt(t *testing.T) {
-	f, err := OpenFile(filepath.Join("test", "encryptSHA1.xlsx"), Options{Password: "password"})
-	assert.NoError(t, err)
-	assert.EqualError(t, f.SaveAs(filepath.Join("test", "BadEncrypt.xlsx"), Options{Password: "password"}), "not support encryption currently")
-}
-
-func TestEncryptionMechanism(t *testing.T) {
-	mechanism, err := encryptionMechanism([]byte{3, 0, 3, 0})
-	assert.Equal(t, mechanism, "extensible")
-	assert.EqualError(t, err, "unsupport encryption mechanism")
-	_, err = encryptionMechanism([]byte{})
-	assert.EqualError(t, err, "unknown encryption mechanism")
-}
-
-func TestHashing(t *testing.T) {
-	assert.Equal(t, hashing("unsupportHashAlgorithm", []byte{}), []uint8([]byte(nil)))
-}

+ 7 - 9
datavalidation.go

@@ -1,13 +1,11 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
+// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
 // this source code is governed by a BSD-style license that can be found in
 // the LICENSE file.
 //
 // Package excelize providing a set of functions that allow you to write to
-// and read from XLSX / XLSM / XLTM files. Supports reading and writing
-// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
-// complex components by high compatibility, and provided streaming API for
-// generating or reading data from a worksheet with huge amounts of data. This
-// library needs Go version 1.15 or later.
+// and read from XLSX files. Support reads and writes XLSX file generated by
+// Microsoft Excel™ 2007 and later. Support save file without losing original
+// charts of XLSX. This library needs Go version 1.10 or later.
 
 package excelize
 
@@ -120,9 +118,9 @@ func (dd *DataValidation) SetDropList(keys []string) error {
 }
 
 // SetRange provides function to set data validation range in drop list.
-func (dd *DataValidation) SetRange(f1, f2 float64, t DataValidationType, o DataValidationOperator) error {
-	formula1 := fmt.Sprintf("%f", f1)
-	formula2 := fmt.Sprintf("%f", f2)
+func (dd *DataValidation) SetRange(f1, f2 int, t DataValidationType, o DataValidationOperator) error {
+	formula1 := fmt.Sprintf("%d", f1)
+	formula2 := fmt.Sprintf("%d", f2)
 	if dataValidationFormulaStrLen+21 < len(dd.Formula1) || dataValidationFormulaStrLen+21 < len(dd.Formula2) {
 		return fmt.Errorf(dataValidationFormulaStrLenErr)
 	}

+ 2 - 2
datavalidation_test.go

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

+ 6 - 7
date.go

@@ -1,17 +1,16 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
+// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
 // this source code is governed by a BSD-style license that can be found in
 // the LICENSE file.
 //
 // Package excelize providing a set of functions that allow you to write to
-// and read from XLSX / XLSM / XLTM files. Supports reading and writing
-// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
-// complex components by high compatibility, and provided streaming API for
-// generating or reading data from a worksheet with huge amounts of data. This
-// library needs Go version 1.15 or later.
+// and read from XLSX files. Support reads and writes XLSX file generated by
+// Microsoft Excel™ 2007 and later. Support save file without losing original
+// charts of XLSX. This library needs Go version 1.10 or later.
 
 package excelize
 
 import (
+	"errors"
 	"math"
 	"time"
 )
@@ -34,7 +33,7 @@ func timeToExcelTime(t time.Time) (float64, error) {
 	// Because for example 1900-01-01 00:00:00 +0300 MSK converts to 1900-01-01 00:00:00 +0230 LMT
 	// probably due to daylight saving.
 	if t.Location() != time.UTC {
-		return 0.0, ErrToExcelTime
+		return 0.0, errors.New("only UTC time expected")
 	}
 
 	if t.Before(excelMinTime1900) {

+ 2 - 2
date_test.go

@@ -17,7 +17,7 @@ var trueExpectedDateList = []dateTest{
 	{0.0000000000000000, time.Date(1899, time.December, 30, 0, 0, 0, 0, time.UTC)},
 	{25569.000000000000, time.Unix(0, 0).UTC()},
 
-	// Expected values extracted from real spreadsheet
+	// Expected values extracted from real xlsx file
 	{1.0000000000000000, time.Date(1900, time.January, 1, 0, 0, 0, 0, time.UTC)},
 	{1.0000115740740740, time.Date(1900, time.January, 1, 0, 0, 1, 0, time.UTC)},
 	{1.0006944444444446, time.Date(1900, time.January, 1, 0, 1, 0, 0, time.UTC)},
@@ -55,7 +55,7 @@ func TestTimeToExcelTime_Timezone(t *testing.T) {
 	for i, test := range trueExpectedDateList {
 		t.Run(fmt.Sprintf("TestData%d", i+1), func(t *testing.T) {
 			_, err := timeToExcelTime(test.GoValue.In(location))
-			assert.EqualError(t, err, ErrToExcelTime.Error())
+			assert.EqualError(t, err, "only UTC time expected")
 		})
 	}
 }

+ 4 - 6
docProps.go

@@ -1,13 +1,11 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
+// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
 // this source code is governed by a BSD-style license that can be found in
 // the LICENSE file.
 //
 // Package excelize providing a set of functions that allow you to write to
-// and read from XLSX / XLSM / XLTM files. Supports reading and writing
-// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
-// complex components by high compatibility, and provided streaming API for
-// generating or reading data from a worksheet with huge amounts of data. This
-// library needs Go version 1.15 or later.
+// and read from XLSX files. Support reads and writes XLSX file generated by
+// Microsoft Excel™ 2007 and later. Support save file without losing original
+// charts of XLSX. This library needs Go version 1.10 or later.
 
 package excelize
 

+ 6 - 8
docProps_test.go

@@ -1,13 +1,11 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
+// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
 // this source code is governed by a BSD-style license that can be found in
 // the LICENSE file.
 //
 // Package excelize providing a set of functions that allow you to write to
-// and read from XLSX / XLSM / XLTM files. Supports reading and writing
-// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
-// complex components by high compatibility, and provided streaming API for
-// generating or reading data from a worksheet with huge amounts of data. This
-// library needs Go version 1.15 or later.
+// and read from XLSX files. Support reads and writes XLSX file generated by
+// Microsoft Excel™ 2007 and later. Support save file without losing original
+// charts of XLSX. This library needs Go version 1.10 or later.
 
 package excelize
 
@@ -45,7 +43,7 @@ func TestSetDocProps(t *testing.T) {
 	f.XLSX["docProps/core.xml"] = nil
 	assert.NoError(t, f.SetDocProps(&DocProperties{}))
 
-	// Test unsupported charset
+	// Test unsupport charset
 	f = NewFile()
 	f.XLSX["docProps/core.xml"] = MacintoshCyrillicCharset
 	assert.EqualError(t, f.SetDocProps(&DocProperties{}), "xml decode error: XML syntax error on line 1: invalid UTF-8")
@@ -63,7 +61,7 @@ func TestGetDocProps(t *testing.T) {
 	_, err = f.GetDocProps()
 	assert.NoError(t, err)
 
-	// Test unsupported charset
+	// Test unsupport charset
 	f = NewFile()
 	f.XLSX["docProps/core.xml"] = MacintoshCyrillicCharset
 	_, err = f.GetDocProps()

+ 51 - 64
drawing.go

@@ -1,13 +1,11 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
+// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
 // this source code is governed by a BSD-style license that can be found in
 // the LICENSE file.
 //
 // Package excelize providing a set of functions that allow you to write to
-// and read from XLSX / XLSM / XLTM files. Supports reading and writing
-// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
-// complex components by high compatibility, and provided streaming API for
-// generating or reading data from a worksheet with huge amounts of data. This
-// library needs Go version 1.15 or later.
+// and read from XLSX files. Support reads and writes XLSX file generated by
+// Microsoft Excel™ 2007 and later. Support save file without losing original
+// charts of XLSX. This library needs Go version 1.10 or later.
 
 package excelize
 
@@ -24,11 +22,11 @@ import (
 
 // prepareDrawing provides a function to prepare drawing ID and XML by given
 // drawingID, worksheet name and default drawingXML.
-func (f *File) prepareDrawing(ws *xlsxWorksheet, drawingID int, sheet, drawingXML string) (int, string) {
+func (f *File) prepareDrawing(xlsx *xlsxWorksheet, drawingID int, sheet, drawingXML string) (int, string) {
 	sheetRelationshipsDrawingXML := "../drawings/drawing" + strconv.Itoa(drawingID) + ".xml"
-	if ws.Drawing != nil {
+	if xlsx.Drawing != nil {
 		// The worksheet already has a picture or chart relationships, use the relationships drawing ../drawings/drawing%d.xml.
-		sheetRelationshipsDrawingXML = f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID)
+		sheetRelationshipsDrawingXML = f.getSheetRelationshipsTargetByID(sheet, xlsx.Drawing.RID)
 		drawingID, _ = strconv.Atoi(strings.TrimSuffix(strings.TrimPrefix(sheetRelationshipsDrawingXML, "../drawings/drawing"), ".xml"))
 		drawingXML = strings.Replace(sheetRelationshipsDrawingXML, "..", "xl", -1)
 	} else {
@@ -42,15 +40,15 @@ func (f *File) prepareDrawing(ws *xlsxWorksheet, drawingID int, sheet, drawingXM
 
 // prepareChartSheetDrawing provides a function to prepare drawing ID and XML
 // by given drawingID, worksheet name and default drawingXML.
-func (f *File) prepareChartSheetDrawing(cs *xlsxChartsheet, drawingID int, sheet string) {
+func (f *File) prepareChartSheetDrawing(xlsx *xlsxChartsheet, drawingID int, sheet string) {
 	sheetRelationshipsDrawingXML := "../drawings/drawing" + strconv.Itoa(drawingID) + ".xml"
 	// Only allow one chart in a chartsheet.
 	sheetRels := "xl/chartsheets/_rels/" + strings.TrimPrefix(f.sheetMap[trimSheetName(sheet)], "xl/chartsheets/") + ".rels"
 	rID := f.addRels(sheetRels, SourceRelationshipDrawingML, sheetRelationshipsDrawingXML, "")
-	f.addSheetNameSpace(sheet, SourceRelationship)
-	cs.Drawing = &xlsxDrawing{
+	xlsx.Drawing = &xlsxDrawing{
 		RID: "rId" + strconv.Itoa(rID),
 	}
+	return
 }
 
 // addChart provides a function to create chart as xl/charts/chart%d.xml by
@@ -58,7 +56,10 @@ func (f *File) prepareChartSheetDrawing(cs *xlsxChartsheet, drawingID int, sheet
 func (f *File) addChart(formatSet *formatChart, comboCharts []*formatChart) {
 	count := f.countCharts()
 	xlsxChartSpace := xlsxChartSpace{
-		XMLNSa:         NameSpaceDrawingML.Value,
+		XMLNSc:         NameSpaceDrawingMLChart,
+		XMLNSa:         NameSpaceDrawingML,
+		XMLNSr:         SourceRelationship,
+		XMLNSc16r2:     SourceRelationshipChart201506,
 		Date1904:       &attrValBool{Val: boolPtr(false)},
 		Lang:           &attrValString{Val: stringPtr("en-US")},
 		RoundedCorners: &attrValBool{Val: boolPtr(false)},
@@ -235,9 +236,6 @@ func (f *File) addChart(formatSet *formatChart, comboCharts []*formatChart) {
 		Bubble:                      f.drawBaseChart,
 		Bubble3D:                    f.drawBaseChart,
 	}
-	if formatSet.Legend.None {
-		xlsxChartSpace.Chart.Legend = nil
-	}
 	addChart := func(c, p *cPlotArea) {
 		immutable, mutable := reflect.ValueOf(c).Elem(), reflect.ValueOf(p).Elem()
 		for i := 0; i < mutable.NumField(); i++ {
@@ -271,7 +269,7 @@ func (f *File) drawBaseChart(formatSet *formatChart) *cPlotArea {
 			Val: stringPtr("clustered"),
 		},
 		VaryColors: &attrValBool{
-			Val: boolPtr(formatSet.VaryColors),
+			Val: boolPtr(true),
 		},
 		Ser:   f.drawChartSeries(formatSet),
 		Shape: f.drawChartShape(formatSet),
@@ -515,7 +513,7 @@ func (f *File) drawDoughnutChart(formatSet *formatChart) *cPlotArea {
 	return &cPlotArea{
 		DoughnutChart: &cCharts{
 			VaryColors: &attrValBool{
-				Val: boolPtr(formatSet.VaryColors),
+				Val: boolPtr(true),
 			},
 			Ser:      f.drawChartSeries(formatSet),
 			HoleSize: &attrValInt{Val: intPtr(75)},
@@ -555,7 +553,7 @@ func (f *File) drawPieChart(formatSet *formatChart) *cPlotArea {
 	return &cPlotArea{
 		PieChart: &cCharts{
 			VaryColors: &attrValBool{
-				Val: boolPtr(formatSet.VaryColors),
+				Val: boolPtr(true),
 			},
 			Ser: f.drawChartSeries(formatSet),
 		},
@@ -568,7 +566,7 @@ func (f *File) drawPie3DChart(formatSet *formatChart) *cPlotArea {
 	return &cPlotArea{
 		Pie3DChart: &cCharts{
 			VaryColors: &attrValBool{
-				Val: boolPtr(formatSet.VaryColors),
+				Val: boolPtr(true),
 			},
 			Ser: f.drawChartSeries(formatSet),
 		},
@@ -584,7 +582,7 @@ func (f *File) drawPieOfPieChart(formatSet *formatChart) *cPlotArea {
 				Val: stringPtr("pie"),
 			},
 			VaryColors: &attrValBool{
-				Val: boolPtr(formatSet.VaryColors),
+				Val: boolPtr(true),
 			},
 			Ser:      f.drawChartSeries(formatSet),
 			SerLines: &attrValString{},
@@ -601,7 +599,7 @@ func (f *File) drawBarOfPieChart(formatSet *formatChart) *cPlotArea {
 				Val: stringPtr("bar"),
 			},
 			VaryColors: &attrValBool{
-				Val: boolPtr(formatSet.VaryColors),
+				Val: boolPtr(true),
 			},
 			Ser:      f.drawChartSeries(formatSet),
 			SerLines: &attrValString{},
@@ -744,17 +742,16 @@ func (f *File) drawChartSeries(formatSet *formatChart) *[]cSer {
 					F: formatSet.Series[k].Name,
 				},
 			},
-			SpPr:             f.drawChartSeriesSpPr(k, formatSet),
-			Marker:           f.drawChartSeriesMarker(k, formatSet),
-			DPt:              f.drawChartSeriesDPt(k, formatSet),
-			DLbls:            f.drawChartSeriesDLbls(formatSet),
-			InvertIfNegative: &attrValBool{Val: boolPtr(false)},
-			Cat:              f.drawChartSeriesCat(formatSet.Series[k], formatSet),
-			Val:              f.drawChartSeriesVal(formatSet.Series[k], formatSet),
-			XVal:             f.drawChartSeriesXVal(formatSet.Series[k], formatSet),
-			YVal:             f.drawChartSeriesYVal(formatSet.Series[k], formatSet),
-			BubbleSize:       f.drawCharSeriesBubbleSize(formatSet.Series[k], formatSet),
-			Bubble3D:         f.drawCharSeriesBubble3D(formatSet),
+			SpPr:       f.drawChartSeriesSpPr(k, formatSet),
+			Marker:     f.drawChartSeriesMarker(k, formatSet),
+			DPt:        f.drawChartSeriesDPt(k, formatSet),
+			DLbls:      f.drawChartSeriesDLbls(formatSet),
+			Cat:        f.drawChartSeriesCat(formatSet.Series[k], formatSet),
+			Val:        f.drawChartSeriesVal(formatSet.Series[k], formatSet),
+			XVal:       f.drawChartSeriesXVal(formatSet.Series[k], formatSet),
+			YVal:       f.drawChartSeriesYVal(formatSet.Series[k], formatSet),
+			BubbleSize: f.drawCharSeriesBubbleSize(formatSet.Series[k], formatSet),
+			Bubble3D:   f.drawCharSeriesBubble3D(formatSet),
 		})
 	}
 	return &ser
@@ -773,11 +770,13 @@ func (f *File) drawChartSeriesSpPr(i int, formatSet *formatChart) *cSpPr {
 		Ln: &aLn{
 			W:   f.ptToEMUs(formatSet.Series[i].Line.Width),
 			Cap: "rnd", // rnd, sq, flat
-			SolidFill: &aSolidFill{
-				SchemeClr: &aSchemeClr{Val: "accent" + strconv.Itoa((formatSet.order+i)%6+1)},
-			},
 		},
 	}
+	if i+formatSet.order < 6 {
+		spPrLine.Ln.SolidFill = &aSolidFill{
+			SchemeClr: &aSchemeClr{Val: "accent" + strconv.Itoa(i+formatSet.order+1)},
+		}
+	}
 	chartSeriesSpPr := map[string]*cSpPr{Line: spPrLine, Scatter: spPrScatter}
 	return chartSeriesSpPr[formatSet.Type]
 }
@@ -844,17 +843,10 @@ func (f *File) drawChartSeriesVal(v formatChartSeries, formatSet *formatChart) *
 // drawChartSeriesMarker provides a function to draw the c:marker element by
 // given data index and format sets.
 func (f *File) drawChartSeriesMarker(i int, formatSet *formatChart) *cMarker {
-	defaultSymbol := map[string]*attrValString{Scatter: {Val: stringPtr("circle")}}
 	marker := &cMarker{
-		Symbol: defaultSymbol[formatSet.Type],
+		Symbol: &attrValString{Val: stringPtr("circle")},
 		Size:   &attrValInt{Val: intPtr(5)},
 	}
-	if symbol := stringPtr(formatSet.Series[i].Marker.Symbol); *symbol != "" {
-		marker.Symbol = &attrValString{Val: symbol}
-	}
-	if size := intPtr(formatSet.Series[i].Marker.Size); *size != 0 {
-		marker.Size = &attrValInt{Val: size}
-	}
 	if i < 6 {
 		marker.SpPr = &cSpPr{
 			SolidFill: &aSolidFill{
@@ -872,7 +864,7 @@ func (f *File) drawChartSeriesMarker(i int, formatSet *formatChart) *cMarker {
 			},
 		}
 	}
-	chartSeriesMarker := map[string]*cMarker{Scatter: marker, Line: marker}
+	chartSeriesMarker := map[string]*cMarker{Scatter: marker}
 	return chartSeriesMarker[formatSet.Type]
 }
 
@@ -940,8 +932,7 @@ func (f *File) drawChartDLbls(formatSet *formatChart) *cDLbls {
 // given format sets.
 func (f *File) drawChartSeriesDLbls(formatSet *formatChart) *cDLbls {
 	dLbls := f.drawChartDLbls(formatSet)
-	chartSeriesDLbls := map[string]*cDLbls{
-		Scatter: nil, Surface3D: nil, WireframeSurface3D: nil, Contour: nil, WireframeContour: nil, Bubble: nil, Bubble3D: nil}
+	chartSeriesDLbls := map[string]*cDLbls{Scatter: nil, Surface3D: nil, WireframeSurface3D: nil, Contour: nil, WireframeContour: nil, Bubble: nil, Bubble3D: nil}
 	if _, ok := chartSeriesDLbls[formatSet.Type]; ok {
 		return nil
 	}
@@ -966,7 +957,7 @@ func (f *File) drawPlotAreaCatAx(formatSet *formatChart) []*cAxs {
 				Max:         max,
 				Min:         min,
 			},
-			Delete: &attrValBool{Val: boolPtr(formatSet.XAxis.None)},
+			Delete: &attrValBool{Val: boolPtr(false)},
 			AxPos:  &attrValString{Val: stringPtr(catAxPos[formatSet.XAxis.ReverseOrder])},
 			NumFmt: &cNumFmt{
 				FormatCode:   "General",
@@ -1007,20 +998,15 @@ func (f *File) drawPlotAreaValAx(formatSet *formatChart) []*cAxs {
 	if formatSet.YAxis.Maximum == 0 {
 		max = nil
 	}
-	var logBase *attrValFloat
-	if formatSet.YAxis.LogBase >= 2 && formatSet.YAxis.LogBase <= 1000 {
-		logBase = &attrValFloat{Val: float64Ptr(formatSet.YAxis.LogBase)}
-	}
 	axs := []*cAxs{
 		{
 			AxID: &attrValInt{Val: intPtr(753999904)},
 			Scaling: &cScaling{
-				LogBase:     logBase,
 				Orientation: &attrValString{Val: stringPtr(orientation[formatSet.YAxis.ReverseOrder])},
 				Max:         max,
 				Min:         min,
 			},
-			Delete: &attrValBool{Val: boolPtr(formatSet.YAxis.None)},
+			Delete: &attrValBool{Val: boolPtr(false)},
 			AxPos:  &attrValString{Val: stringPtr(valAxPos[formatSet.YAxis.ReverseOrder])},
 			NumFmt: &cNumFmt{
 				FormatCode:   chartValAxNumFmtFormatCode[formatSet.Type],
@@ -1069,7 +1055,7 @@ func (f *File) drawPlotAreaSerAx(formatSet *formatChart) []*cAxs {
 				Max:         max,
 				Min:         min,
 			},
-			Delete:     &attrValBool{Val: boolPtr(formatSet.YAxis.None)},
+			Delete:     &attrValBool{Val: boolPtr(false)},
 			AxPos:      &attrValString{Val: stringPtr(catAxPos[formatSet.XAxis.ReverseOrder])},
 			TickLblPos: &attrValString{Val: stringPtr("nextTo")},
 			SpPr:       f.drawPlotAreaSpPr(),
@@ -1149,8 +1135,8 @@ func (f *File) drawingParser(path string) (*xlsxWsDr, int) {
 
 	if f.Drawings[path] == nil {
 		content := xlsxWsDr{}
-		content.A = NameSpaceDrawingML.Value
-		content.Xdr = NameSpaceDrawingMLSpreadSheet.Value
+		content.A = NameSpaceDrawingML
+		content.Xdr = NameSpaceDrawingMLSpreadSheet
 		if _, ok = f.XLSX[path]; ok { // Append Model
 			decodeWsDr := decodeWsDr{}
 			if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(path)))).
@@ -1189,7 +1175,7 @@ func (f *File) addDrawingChart(sheet, drawingXML, cell string, width, height, rI
 
 	width = int(float64(width) * formatSet.XScale)
 	height = int(float64(height) * formatSet.YScale)
-	colStart, rowStart, colEnd, rowEnd, x2, y2 :=
+	colStart, rowStart, _, _, colEnd, rowEnd, x2, y2 :=
 		f.positionObjectPixels(sheet, colIdx, rowIdx, formatSet.OffsetX, formatSet.OffsetY, width, height)
 	content, cNvPrID := f.drawingParser(drawingXML)
 	twoCellAnchor := xdrCellAnchor{}
@@ -1216,10 +1202,10 @@ func (f *File) addDrawingChart(sheet, drawingXML, cell string, width, height, rI
 		},
 		Graphic: &xlsxGraphic{
 			GraphicData: &xlsxGraphicData{
-				URI: NameSpaceDrawingMLChart.Value,
+				URI: NameSpaceDrawingMLChart,
 				Chart: &xlsxChart{
-					C:   NameSpaceDrawingMLChart.Value,
-					R:   SourceRelationship.Value,
+					C:   NameSpaceDrawingMLChart,
+					R:   SourceRelationship,
 					RID: "rId" + strconv.Itoa(rID),
 				},
 			},
@@ -1256,10 +1242,10 @@ func (f *File) addSheetDrawingChart(drawingXML string, rID int, formatSet *forma
 		},
 		Graphic: &xlsxGraphic{
 			GraphicData: &xlsxGraphicData{
-				URI: NameSpaceDrawingMLChart.Value,
+				URI: NameSpaceDrawingMLChart,
 				Chart: &xlsxChart{
-					C:   NameSpaceDrawingMLChart.Value,
-					R:   SourceRelationship.Value,
+					C:   NameSpaceDrawingMLChart,
+					R:   SourceRelationship,
 					RID: "rId" + strconv.Itoa(rID),
 				},
 			},
@@ -1273,6 +1259,7 @@ func (f *File) addSheetDrawingChart(drawingXML string, rID int, formatSet *forma
 	}
 	content.AbsoluteAnchor = append(content.AbsoluteAnchor, &absoluteAnchor)
 	f.Drawings[drawingXML] = content
+	return
 }
 
 // deleteDrawing provides a function to delete chart graphic frame by given by

+ 5 - 7
drawing_test.go

@@ -1,13 +1,11 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
+// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
 // this source code is governed by a BSD-style license that can be found in
 // the LICENSE file.
 //
 // Package excelize providing a set of functions that allow you to write to
-// and read from XLSX / XLSM / XLTM files. Supports reading and writing
-// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
-// complex components by high compatibility, and provided streaming API for
-// generating or reading data from a worksheet with huge amounts of data. This
-// library needs Go version 1.15 or later.
+// and read from XLSX files. Support reads and writes XLSX file generated by
+// Microsoft Excel™ 2007 and later. Support save file without losing original
+// charts of XLSX. This library needs Go version 1.10 or later.
 
 package excelize
 
@@ -24,6 +22,6 @@ func TestDrawingParser(t *testing.T) {
 	}
 	// Test with one cell anchor
 	f.drawingParser("wsDr")
-	// Test with unsupported charset
+	// Test with unsupport charset
 	f.drawingParser("charset")
 }

+ 5 - 51
errors.go

@@ -1,20 +1,15 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
+// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
 // this source code is governed by a BSD-style license that can be found in
 // the LICENSE file.
 //
 // Package excelize providing a set of functions that allow you to write to
-// and read from XLSX / XLSM / XLTM files. Supports reading and writing
-// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
-// complex components by high compatibility, and provided streaming API for
-// generating or reading data from a worksheet with huge amounts of data. This
-// library needs Go version 1.15 or later.
+// and read from XLSX files. Support reads and writes XLSX file generated by
+// Microsoft Excel™ 2007 and later. Support save file without losing original
+// charts of XLSX. This library needs Go version 1.10 or later.
 
 package excelize
 
-import (
-	"errors"
-	"fmt"
-)
+import "fmt"
 
 func newInvalidColumnNameError(col string) error {
 	return fmt.Errorf("invalid column name %q", col)
@@ -31,44 +26,3 @@ func newInvalidCellNameError(cell string) error {
 func newInvalidExcelDateError(dateValue float64) error {
 	return fmt.Errorf("invalid date value %f, negative values are not supported supported", dateValue)
 }
-
-var (
-	// ErrStreamSetColWidth defined the error message on set column width in
-	// stream writing mode.
-	ErrStreamSetColWidth = errors.New("must call the SetColWidth function before the SetRow function")
-	// ErrColumnNumber defined the error message on receive an invalid column
-	// number.
-	ErrColumnNumber = errors.New("column number exceeds maximum limit")
-	// ErrColumnWidth defined the error message on receive an invalid column
-	// width.
-	ErrColumnWidth = errors.New("the width of the column must be smaller than or equal to 255 characters")
-	// ErrOutlineLevel defined the error message on receive an invalid outline
-	// level number.
-	ErrOutlineLevel = errors.New("invalid outline level")
-	// ErrCoordinates defined the error message on invalid coordinates tuples
-	// length.
-	ErrCoordinates = errors.New("coordinates length must be 4")
-	// ErrExistsWorksheet defined the error message on given worksheet already
-	// exists.
-	ErrExistsWorksheet = errors.New("the same name worksheet already exists")
-	// ErrTotalSheetHyperlinks defined the error message on hyperlinks count
-	// overflow.
-	ErrTotalSheetHyperlinks = errors.New("over maximum limit hyperlinks in a worksheet")
-	// ErrInvalidFormula defined the error message on receive an invalid
-	// formula.
-	ErrInvalidFormula = errors.New("formula not valid")
-	// ErrAddVBAProject defined the error message on add the VBA project in
-	// the workbook.
-	ErrAddVBAProject = errors.New("unsupported VBA project extension")
-	// ErrToExcelTime defined the error message on receive a not UTC time.
-	ErrToExcelTime = errors.New("only UTC time expected")
-	// ErrMaxRowHeight defined the error message on receive an invalid row
-	// height.
-	ErrMaxRowHeight = errors.New("the height of the row must be smaller than or equal to 409 points")
-	// ErrImgExt defined the error message on receive an unsupported image
-	// extension.
-	ErrImgExt = errors.New("unsupported image extension")
-	// ErrMaxFileNameLength defined the error message on receive the file name
-	// length overflow.
-	ErrMaxFileNameLength = errors.New("file name length exceeds maximum limit")
-)

+ 55 - 100
excelize.go

@@ -1,13 +1,11 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
+// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
 // this source code is governed by a BSD-style license that can be found in
 // the LICENSE file.
 
 // Package excelize providing a set of functions that allow you to write to
-// and read from XLSX / XLSM / XLTM files. Supports reading and writing
-// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
-// complex components by high compatibility, and provided streaming API for
-// generating or reading data from a worksheet with huge amounts of data. This
-// library needs Go version 1.15 or later.
+// and read from XLSX files. Support reads and writes XLSX file generated by
+// Microsoft Excel™ 2007 and later. Support save file without losing original
+// charts of XLSX. This library needs Go version 1.10 or later.
 //
 // See https://xuri.me/excelize for more information about this package.
 package excelize
@@ -16,6 +14,7 @@ import (
 	"archive/zip"
 	"bytes"
 	"encoding/xml"
+	"errors"
 	"fmt"
 	"io"
 	"io/ioutil"
@@ -23,26 +22,20 @@ import (
 	"path"
 	"strconv"
 	"strings"
-	"sync"
 
 	"golang.org/x/net/html/charset"
 )
 
-// File define a populated spreadsheet file struct.
+// File define a populated XLSX file struct.
 type File struct {
-	sync.Mutex
-	options          *Options
-	xmlAttr          map[string][]xml.Attr
 	checked          map[string]bool
 	sheetMap         map[string]string
-	streams          map[string]*StreamWriter
 	CalcChain        *xlsxCalcChain
 	Comments         map[string]*xlsxComments
 	ContentTypes     *xlsxTypes
 	Drawings         map[string]*xlsxWsDr
 	Path             string
 	SharedStrings    *xlsxSST
-	sharedStringsMap map[string]int
 	Sheet            map[string]*xlsxWorksheet
 	SheetCount       int
 	Styles           *xlsxStyleSheet
@@ -57,28 +50,15 @@ type File struct {
 
 type charsetTranscoderFn func(charset string, input io.Reader) (rdr io.Reader, err error)
 
-// Options define the options for open spreadsheet.
-type Options struct {
-	Password string
-}
-
-// OpenFile take the name of an spreadsheet file and returns a populated spreadsheet file struct
-// for it. For example, open spreadsheet with password protection:
-//
-//    f, err := excelize.OpenFile("Book1.xlsx", excelize.Options{Password: "password"})
-//    if err != nil {
-//        return
-//    }
-//
-// Note that the excelize just support decrypt and not support encrypt currently, the spreadsheet
-// saved by Save and SaveAs will be without password unprotected.
-func OpenFile(filename string, opt ...Options) (*File, error) {
+// OpenFile take the name of an XLSX file and returns a populated XLSX file
+// struct for it.
+func OpenFile(filename string) (*File, error) {
 	file, err := os.Open(filename)
 	if err != nil {
 		return nil, err
 	}
 	defer file.Close()
-	f, err := OpenReader(file, opt...)
+	f, err := OpenReader(file)
 	if err != nil {
 		return nil, err
 	}
@@ -89,12 +69,10 @@ func OpenFile(filename string, opt ...Options) (*File, error) {
 // newFile is object builder
 func newFile() *File {
 	return &File{
-		xmlAttr:          make(map[string][]xml.Attr),
 		checked:          make(map[string]bool),
 		sheetMap:         make(map[string]string),
 		Comments:         make(map[string]*xlsxComments),
 		Drawings:         make(map[string]*xlsxWsDr),
-		sharedStringsMap: make(map[string]int),
 		Sheet:            make(map[string]*xlsxWorksheet),
 		DecodeVMLDrawing: make(map[string]*decodeVmlDrawing),
 		VMLDrawing:       make(map[string]*vmlDrawing),
@@ -103,25 +81,26 @@ func newFile() *File {
 	}
 }
 
-// OpenReader read data stream from io.Reader and return a populated
-// spreadsheet file.
-func OpenReader(r io.Reader, opt ...Options) (*File, error) {
+// OpenReader take an io.Reader and return a populated XLSX file.
+func OpenReader(r io.Reader) (*File, error) {
 	b, err := ioutil.ReadAll(r)
 	if err != nil {
 		return nil, err
 	}
-	f := newFile()
-	if bytes.Contains(b, oleIdentifier) && len(opt) > 0 {
-		for _, o := range opt {
-			f.options = &o
-		}
-		b, err = Decrypt(b, f.options)
-		if err != nil {
-			return nil, fmt.Errorf("decrypted file failed")
-		}
-	}
+
 	zr, err := zip.NewReader(bytes.NewReader(b), int64(len(b)))
 	if err != nil {
+		identifier := []byte{
+			// checking protect workbook by [MS-OFFCRYPTO] - v20181211 3.1 FeatureIdentifier
+			0x3c, 0x00, 0x00, 0x00, 0x4d, 0x00, 0x69, 0x00, 0x63, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x73, 0x00,
+			0x6f, 0x00, 0x66, 0x00, 0x74, 0x00, 0x2e, 0x00, 0x43, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x74, 0x00,
+			0x61, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x72, 0x00, 0x2e, 0x00, 0x44, 0x00, 0x61, 0x00,
+			0x74, 0x00, 0x61, 0x00, 0x53, 0x00, 0x70, 0x00, 0x61, 0x00, 0x63, 0x00, 0x65, 0x00, 0x73, 0x00,
+			0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+		}
+		if bytes.Contains(b, identifier) {
+			return nil, errors.New("not support encrypted file currently")
+		}
 		return nil, err
 	}
 
@@ -129,6 +108,7 @@ func OpenReader(r io.Reader, opt ...Options) (*File, error) {
 	if err != nil {
 		return nil, err
 	}
+	f := newFile()
 	f.SheetCount, f.XLSX = sheetCount, file
 	f.CalcChain = f.calcChainReader()
 	f.sheetMap = f.getSheetMap()
@@ -158,16 +138,14 @@ func (f *File) setDefaultTimeStyle(sheet, axis string, format int) error {
 	}
 	if s == 0 {
 		style, _ := f.NewStyle(&Style{NumFmt: format})
-		err = f.SetCellStyle(sheet, axis, axis, style)
+		_ = f.SetCellStyle(sheet, axis, axis, style)
 	}
 	return err
 }
 
 // workSheetReader provides a function to get the pointer to the structure
 // after deserialization by given worksheet name.
-func (f *File) workSheetReader(sheet string) (ws *xlsxWorksheet, err error) {
-	f.Lock()
-	defer f.Unlock()
+func (f *File) workSheetReader(sheet string) (xlsx *xlsxWorksheet, err error) {
 	var (
 		name string
 		ok   bool
@@ -177,18 +155,14 @@ func (f *File) workSheetReader(sheet string) (ws *xlsxWorksheet, err error) {
 		err = fmt.Errorf("sheet %s is not exist", sheet)
 		return
 	}
-	if ws = f.Sheet[name]; f.Sheet[name] == nil {
+	if xlsx = f.Sheet[name]; f.Sheet[name] == nil {
 		if strings.HasPrefix(name, "xl/chartsheets") {
 			err = fmt.Errorf("sheet %s is chart sheet", sheet)
 			return
 		}
-		ws = new(xlsxWorksheet)
-		if _, ok := f.xmlAttr[name]; !ok {
-			d := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(name))))
-			f.xmlAttr[name] = append(f.xmlAttr[name], getRootElement(d)...)
-		}
+		xlsx = new(xlsxWorksheet)
 		if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(name)))).
-			Decode(ws); err != nil && err != io.EOF {
+			Decode(xlsx); err != nil && err != io.EOF {
 			err = fmt.Errorf("xml decode error: %s", err)
 			return
 		}
@@ -197,13 +171,13 @@ func (f *File) workSheetReader(sheet string) (ws *xlsxWorksheet, err error) {
 			f.checked = make(map[string]bool)
 		}
 		if ok = f.checked[name]; !ok {
-			checkSheet(ws)
-			if err = checkRow(ws); err != nil {
+			checkSheet(xlsx)
+			if err = checkRow(xlsx); err != nil {
 				return
 			}
 			f.checked[name] = true
 		}
-		f.Sheet[name] = ws
+		f.Sheet[name] = xlsx
 	}
 
 	return
@@ -211,61 +185,37 @@ func (f *File) workSheetReader(sheet string) (ws *xlsxWorksheet, err error) {
 
 // checkSheet provides a function to fill each row element and make that is
 // continuous in a worksheet of XML.
-func checkSheet(ws *xlsxWorksheet) {
-	var row int
-	for _, r := range ws.SheetData.Row {
-		if r.R != 0 && r.R > row {
-			row = r.R
-			continue
-		}
-		if r.R != row {
-			row++
+func checkSheet(xlsx *xlsxWorksheet) {
+	row := len(xlsx.SheetData.Row)
+	if row >= 1 {
+		lastRow := xlsx.SheetData.Row[row-1].R
+		if lastRow >= row {
+			row = lastRow
 		}
 	}
 	sheetData := xlsxSheetData{Row: make([]xlsxRow, row)}
-	row = 0
-	for _, r := range ws.SheetData.Row {
-		if r.R == row {
-			sheetData.Row[r.R-1].C = append(sheetData.Row[r.R-1].C, r.C...)
-			continue
-		}
-		if r.R != 0 {
-			sheetData.Row[r.R-1] = r
-			row = r.R
-			continue
-		}
-		row++
-		r.R = row
-		sheetData.Row[row-1] = r
+	for _, r := range xlsx.SheetData.Row {
+		sheetData.Row[r.R-1] = r
 	}
 	for i := 1; i <= row; i++ {
 		sheetData.Row[i-1].R = i
 	}
-	ws.SheetData = sheetData
+	xlsx.SheetData = sheetData
 }
 
 // addRels provides a function to add relationships by given XML path,
 // relationship type, target and target mode.
 func (f *File) addRels(relPath, relType, target, targetMode string) int {
-	var uniqPart = map[string]string{
-		SourceRelationshipSharedStrings: "/xl/sharedStrings.xml",
-	}
 	rels := f.relsReader(relPath)
 	if rels == nil {
 		rels = &xlsxRelationships{}
 	}
 	var rID int
-	for idx, rel := range rels.Relationships {
+	for _, rel := range rels.Relationships {
 		ID, _ := strconv.Atoi(strings.TrimPrefix(rel.ID, "rId"))
 		if ID > rID {
 			rID = ID
 		}
-		if relType == rel.Type {
-			if partName, ok := uniqPart[rel.Type]; ok {
-				rels.Relationships[idx].Target = partName
-				return rID
-			}
-		}
 	}
 	rID++
 	var ID bytes.Buffer
@@ -281,6 +231,14 @@ func (f *File) addRels(relPath, relType, target, targetMode string) int {
 	return rID
 }
 
+// replaceRelationshipsNameSpaceBytes provides a function to replace
+// XML tags to self-closing for compatible Microsoft Office Excel 2007.
+func replaceRelationshipsNameSpaceBytes(contentMarshal []byte) []byte {
+	var oldXmlns = stringToBytes(` xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">`)
+	var newXmlns = []byte(templateNamespaceIDMap)
+	return bytesReplace(contentMarshal, oldXmlns, newXmlns, -1)
+}
+
 // UpdateLinkedValue fix linked values within a spreadsheet are not updating in
 // Office Excel 2007 and 2010. This function will be remove value tag when met a
 // cell have a linked value. Reference
@@ -310,12 +268,9 @@ func (f *File) UpdateLinkedValue() error {
 	wb := f.workbookReader()
 	// recalculate formulas
 	wb.CalcPr = nil
-	for _, name := range f.GetSheetList() {
+	for _, name := range f.GetSheetMap() {
 		xlsx, err := f.workSheetReader(name)
 		if err != nil {
-			if err.Error() == fmt.Sprintf("sheet %s is chart sheet", trimSheetName(name)) {
-				continue
-			}
 			return err
 		}
 		for indexR := range xlsx.SheetData.Row {
@@ -347,13 +302,13 @@ func (f *File) AddVBAProject(bin string) error {
 	var err error
 	// Check vbaProject.bin exists first.
 	if _, err = os.Stat(bin); os.IsNotExist(err) {
-		return fmt.Errorf("stat %s: no such file or directory", bin)
+		return err
 	}
 	if path.Ext(bin) != ".bin" {
-		return ErrAddVBAProject
+		return errors.New("unsupported VBA project extension")
 	}
 	f.setContentTypePartVBAProjectExtensions()
-	wb := f.relsReader(f.getWorkbookRelsPath())
+	wb := f.relsReader("xl/_rels/workbook.xml.rels")
 	var rID int
 	var ok bool
 	for _, rel := range wb.Relationships {

+ 78 - 101
excelize_test.go

@@ -22,7 +22,7 @@ import (
 )
 
 func TestOpenFile(t *testing.T) {
-	// Test update the spreadsheet file.
+	// Test update a XLSX file.
 	f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
 	assert.NoError(t, err)
 
@@ -54,7 +54,7 @@ func TestOpenFile(t *testing.T) {
 
 	assert.NoError(t, f.SetCellStr("Sheet2", "C11", "Knowns"))
 	// Test max characters in a cell.
-	assert.NoError(t, f.SetCellStr("Sheet2", "D11", strings.Repeat("c", TotalCellChars+2)))
+	assert.NoError(t, f.SetCellStr("Sheet2", "D11", strings.Repeat("c", 32769)))
 	f.NewSheet(":\\/?*[]Maximum 31 characters allowed in sheet title.")
 	// Test set worksheet name with illegal name.
 	f.SetSheetName("Maximum 31 characters allowed i", "[Rename]:\\/?* Maximum 31 characters allowed in sheet title.")
@@ -144,7 +144,7 @@ func TestOpenFile(t *testing.T) {
 
 	assert.NoError(t, f.SetCellValue("Sheet2", "G2", nil))
 
-	assert.EqualError(t, f.SetCellValue("Sheet2", "G4", time.Now()), ErrToExcelTime.Error())
+	assert.EqualError(t, f.SetCellValue("Sheet2", "G4", time.Now()), "only UTC time expected")
 
 	assert.NoError(t, f.SetCellValue("Sheet2", "G4", time.Now().UTC()))
 	// 02:46:40
@@ -154,11 +154,11 @@ func TestOpenFile(t *testing.T) {
 	// Test read cell value with given axis large than exists row.
 	_, err = f.GetCellValue("Sheet2", "E231")
 	assert.NoError(t, err)
-	// Test get active worksheet of spreadsheet and get worksheet name of spreadsheet by given worksheet index.
+	// Test get active worksheet of XLSX and get worksheet name of XLSX by given worksheet index.
 	f.GetSheetName(f.GetActiveSheetIndex())
-	// Test get worksheet index of spreadsheet by given worksheet name.
+	// Test get worksheet index of XLSX by given worksheet name.
 	f.GetSheetIndex("Sheet1")
-	// Test get worksheet name of spreadsheet by given invalid worksheet index.
+	// Test get worksheet name of XLSX by given invalid worksheet index.
 	f.GetSheetName(4)
 	// Test get worksheet map of workbook.
 	f.GetSheetMap()
@@ -166,7 +166,6 @@ func TestOpenFile(t *testing.T) {
 		assert.NoError(t, f.SetCellStr("Sheet2", "c"+strconv.Itoa(i), strconv.Itoa(i)))
 	}
 	assert.NoError(t, f.SaveAs(filepath.Join("test", "TestOpenFile.xlsx")))
-	assert.EqualError(t, f.SaveAs(filepath.Join("test", strings.Repeat("c", 199), ".xlsx")), ErrMaxFileNameLength.Error())
 }
 
 func TestSaveFile(t *testing.T) {
@@ -201,22 +200,14 @@ func TestCharsetTranscoder(t *testing.T) {
 func TestOpenReader(t *testing.T) {
 	_, err := OpenReader(strings.NewReader(""))
 	assert.EqualError(t, err, "zip: not a valid zip file")
-	_, err = OpenReader(bytes.NewReader(oleIdentifier), Options{Password: "password"})
-	assert.EqualError(t, err, "decrypted file failed")
-
-	// Test open password protected spreadsheet created by Microsoft Office Excel 2010.
-	f, err := OpenFile(filepath.Join("test", "encryptSHA1.xlsx"), Options{Password: "password"})
-	assert.NoError(t, err)
-	val, err := f.GetCellValue("Sheet1", "A1")
-	assert.NoError(t, err)
-	assert.Equal(t, "SECRET", val)
-
-	// Test open password protected spreadsheet created by LibreOffice 7.0.0.3.
-	f, err = OpenFile(filepath.Join("test", "encryptAES.xlsx"), Options{Password: "password"})
-	assert.NoError(t, err)
-	val, err = f.GetCellValue("Sheet1", "A1")
-	assert.NoError(t, err)
-	assert.Equal(t, "SECRET", val)
+	_, err = OpenReader(bytes.NewReader([]byte{
+		0x3c, 0x00, 0x00, 0x00, 0x4d, 0x00, 0x69, 0x00, 0x63, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x73, 0x00,
+		0x6f, 0x00, 0x66, 0x00, 0x74, 0x00, 0x2e, 0x00, 0x43, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x74, 0x00,
+		0x61, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x72, 0x00, 0x2e, 0x00, 0x44, 0x00, 0x61, 0x00,
+		0x74, 0x00, 0x61, 0x00, 0x53, 0x00, 0x70, 0x00, 0x61, 0x00, 0x63, 0x00, 0x65, 0x00, 0x73, 0x00,
+		0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+	}))
+	assert.EqualError(t, err, "not support encrypted file currently")
 
 	// Test unexpected EOF.
 	var b bytes.Buffer
@@ -229,22 +220,6 @@ func TestOpenReader(t *testing.T) {
 
 	_, err = OpenReader(r)
 	assert.EqualError(t, err, "unexpected EOF")
-
-	_, err = OpenReader(bytes.NewReader([]byte{
-		0x50, 0x4b, 0x03, 0x04, 0x0a, 0x00, 0x09, 0x00, 0x63, 0x00, 0x47, 0xa3, 0xb6, 0x50, 0x00, 0x00,
-		0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0b, 0x00, 0x70, 0x61,
-		0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x01, 0x99, 0x07, 0x00, 0x02, 0x00, 0x41, 0x45, 0x03, 0x00,
-		0x00, 0x21, 0x06, 0x59, 0xc0, 0x12, 0xf3, 0x19, 0xc7, 0x51, 0xd1, 0xc9, 0x31, 0xcb, 0xcc, 0x8a,
-		0xe1, 0x44, 0xe1, 0x56, 0x20, 0x24, 0x1f, 0xba, 0x09, 0xda, 0x53, 0xd5, 0xef, 0x50, 0x4b, 0x07,
-		0x08, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x4b, 0x01,
-		0x02, 0x1f, 0x00, 0x0a, 0x00, 0x09, 0x00, 0x63, 0x00, 0x47, 0xa3, 0xb6, 0x50, 0x00, 0x00, 0x00,
-		0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00,
-		0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x61, 0x73, 0x73, 0x77,
-		0x6f, 0x72, 0x64, 0x01, 0x99, 0x07, 0x00, 0x02, 0x00, 0x41, 0x45, 0x03, 0x00, 0x00, 0x50, 0x4b,
-		0x05, 0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x41, 0x00, 0x00, 0x00, 0x5d, 0x00,
-		0x00, 0x00, 0x00, 0x00,
-	}))
-	assert.EqualError(t, err, "zip: unsupported compression algorithm")
 }
 
 func TestBrokenFile(t *testing.T) {
@@ -257,19 +232,19 @@ func TestBrokenFile(t *testing.T) {
 
 	t.Run("SaveAsEmptyStruct", func(t *testing.T) {
 		// Test write file with broken file struct with given path.
-		assert.NoError(t, f.SaveAs(filepath.Join("test", "BadWorkbook.SaveAsEmptyStruct.xlsx")))
+		assert.NoError(t, f.SaveAs(filepath.Join("test", "BrokenFile.SaveAsEmptyStruct.xlsx")))
 	})
 
 	t.Run("OpenBadWorkbook", func(t *testing.T) {
 		// Test set active sheet without BookViews and Sheets maps in xl/workbook.xml.
 		f3, err := OpenFile(filepath.Join("test", "BadWorkbook.xlsx"))
 		f3.GetActiveSheetIndex()
-		f3.SetActiveSheet(1)
+		f3.SetActiveSheet(2)
 		assert.NoError(t, err)
 	})
 
 	t.Run("OpenNotExistsFile", func(t *testing.T) {
-		// Test open a spreadsheet file with given illegal path.
+		// Test open a XLSX file with given illegal path.
 		_, err := OpenFile(filepath.Join("test", "NotExistsFile.xlsx"))
 		if assert.Error(t, err) {
 			assert.True(t, os.IsNotExist(err), "Expected os.IsNotExists(err) == true")
@@ -278,7 +253,7 @@ func TestBrokenFile(t *testing.T) {
 }
 
 func TestNewFile(t *testing.T) {
-	// Test create a spreadsheet file.
+	// Test create a XLSX file.
 	f := NewFile()
 	f.NewSheet("Sheet1")
 	f.NewSheet("XLSXSheet2")
@@ -326,13 +301,6 @@ func TestSetCellHyperLink(t *testing.T) {
 	assert.NoError(t, f.SetCellHyperLink("Sheet2", "C1", "https://github.com/360EntSecGroup-Skylar/excelize", "External"))
 	// Test add Location hyperlink in a work sheet.
 	assert.NoError(t, f.SetCellHyperLink("Sheet2", "D6", "Sheet1!D8", "Location"))
-	// Test add Location hyperlink with display & tooltip in a work sheet.
-	display := "Display value"
-	tooltip := "Hover text"
-	assert.NoError(t, f.SetCellHyperLink("Sheet2", "D7", "Sheet1!D9", "Location", HyperlinkOpts{
-		Display: &display,
-		Tooltip: &tooltip,
-	}))
 
 	assert.EqualError(t, f.SetCellHyperLink("Sheet2", "C3", "Sheet1!D8", ""), `invalid link type ""`)
 
@@ -344,7 +312,7 @@ func TestSetCellHyperLink(t *testing.T) {
 	_, err = f.workSheetReader("Sheet1")
 	assert.NoError(t, err)
 	f.Sheet["xl/worksheets/sheet1.xml"].Hyperlinks = &xlsxHyperlinks{Hyperlink: make([]xlsxHyperlink, 65530)}
-	assert.EqualError(t, f.SetCellHyperLink("Sheet1", "A65531", "https://github.com/360EntSecGroup-Skylar/excelize", "External"), ErrTotalSheetHyperlinks.Error())
+	assert.EqualError(t, f.SetCellHyperLink("Sheet1", "A65531", "https://github.com/360EntSecGroup-Skylar/excelize", "External"), "over maximum limit hyperlinks in a worksheet")
 
 	f = NewFile()
 	_, err = f.workSheetReader("Sheet1")
@@ -449,7 +417,7 @@ func TestSetSheetBackgroundErrors(t *testing.T) {
 	}
 
 	err = f.SetSheetBackground("Sheet2", filepath.Join("test", "Book1.xlsx"))
-	assert.EqualError(t, err, ErrImgExt.Error())
+	assert.EqualError(t, err, "unsupported image extension")
 }
 
 // TestWriteArrayFormula tests the extended options of SetCellFormula by writing an array function
@@ -549,10 +517,10 @@ func TestWriteArrayFormula(t *testing.T) {
 		assert.NoError(t, f.SetCellFormula("Sheet1", avgCell, fmt.Sprintf("ROUND(AVERAGEIF(%s,%s,%s),0)", assocRange, nameCell, valRange)))
 
 		ref := stdevCell + ":" + stdevCell
-		arr := STCellFormulaTypeArray
+		t := STCellFormulaTypeArray
 		// Use an array formula for standard deviation
-		assert.NoError(t, f.SetCellFormula("Sheet1", stdevCell, fmt.Sprintf("ROUND(STDEVP(IF(%s=%s,%s)),0)", assocRange, nameCell, valRange),
-			FormulaOpts{}, FormulaOpts{Type: &arr}, FormulaOpts{Ref: &ref}))
+		f.SetCellFormula("Sheet1", stdevCell, fmt.Sprintf("ROUND(STDEVP(IF(%s=%s,%s)),0)", assocRange, nameCell, valRange),
+			FormulaOpts{}, FormulaOpts{Type: &t}, FormulaOpts{Ref: &ref})
 	}
 
 	assert.NoError(t, f.SaveAs(filepath.Join("test", "TestWriteArrayFormula.xlsx")))
@@ -784,14 +752,16 @@ func TestSetCellStyleCustomNumberFormat(t *testing.T) {
 	assert.NoError(t, f.SetCellValue("Sheet1", "A1", 42920.5))
 	assert.NoError(t, f.SetCellValue("Sheet1", "A2", 42920.5))
 	style, err := f.NewStyle(`{"custom_number_format": "[$-380A]dddd\\,\\ dd\" de \"mmmm\" de \"yyyy;@"}`)
-	assert.NoError(t, err)
+	if err != nil {
+		t.Log(err)
+	}
 	assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "A1", style))
-	style, err = f.NewStyle(`{"custom_number_format": "[$-380A]dddd\\,\\ dd\" de \"mmmm\" de \"yyyy;@","font":{"color":"#9A0511"}}`)
-	assert.NoError(t, err)
+	style, err = f.NewStyle(`{"custom_number_format": "[$-380A]dddd\\,\\ dd\" de \"mmmm\" de \"yyyy;@"}`)
+	if err != nil {
+		t.Log(err)
+	}
 	assert.NoError(t, f.SetCellStyle("Sheet1", "A2", "A2", style))
 
-	_, err = f.NewStyle(`{"custom_number_format": "[$-380A]dddd\\,\\ dd\" de \"mmmm\" de \"yy;@"}`)
-	assert.NoError(t, err)
 	assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellStyleCustomNumberFormat.xlsx")))
 }
 
@@ -804,15 +774,21 @@ func TestSetCellStyleFill(t *testing.T) {
 	var style int
 	// Test set fill for cell with invalid parameter.
 	style, err = f.NewStyle(`{"fill":{"type":"gradient","color":["#FFFFFF","#E0EBF5"],"shading":6}}`)
-	assert.NoError(t, err)
+	if !assert.NoError(t, err) {
+		t.FailNow()
+	}
 	assert.NoError(t, f.SetCellStyle("Sheet1", "O23", "O23", style))
 
 	style, err = f.NewStyle(`{"fill":{"type":"gradient","color":["#FFFFFF"],"shading":1}}`)
-	assert.NoError(t, err)
+	if !assert.NoError(t, err) {
+		t.FailNow()
+	}
 	assert.NoError(t, f.SetCellStyle("Sheet1", "O23", "O23", style))
 
 	style, err = f.NewStyle(`{"fill":{"type":"pattern","color":[],"pattern":1}}`)
-	assert.NoError(t, err)
+	if !assert.NoError(t, err) {
+		t.FailNow()
+	}
 	assert.NoError(t, f.SetCellStyle("Sheet1", "O23", "O23", style))
 
 	style, err = f.NewStyle(`{"fill":{"type":"pattern","color":["#E0EBF5"],"pattern":19}}`)
@@ -932,7 +908,7 @@ func TestCopySheet(t *testing.T) {
 	}
 
 	idx := f.NewSheet("CopySheet")
-	assert.NoError(t, f.CopySheet(0, idx))
+	assert.NoError(t, f.CopySheet(1, idx))
 
 	assert.NoError(t, f.SetCellValue("CopySheet", "F1", "Hello"))
 	val, err := f.GetCellValue("Sheet1", "F1")
@@ -948,8 +924,8 @@ func TestCopySheetError(t *testing.T) {
 		t.FailNow()
 	}
 
-	assert.EqualError(t, f.copySheet(-1, -2), "sheet  is not exist")
-	if !assert.EqualError(t, f.CopySheet(-1, -2), "invalid worksheet index") {
+	assert.EqualError(t, f.copySheet(0, -1), "sheet  is not exist")
+	if !assert.EqualError(t, f.CopySheet(0, -1), "invalid worksheet index") {
 		t.FailNow()
 	}
 
@@ -961,6 +937,17 @@ func TestGetSheetComments(t *testing.T) {
 	assert.Equal(t, "", f.getSheetComments("sheet0"))
 }
 
+func TestSetActiveSheet(t *testing.T) {
+	f := NewFile()
+	f.WorkBook.BookViews = nil
+	f.SetActiveSheet(1)
+	f.WorkBook.BookViews = &xlsxBookViews{WorkBookView: []xlsxWorkBookView{}}
+	f.Sheet["xl/worksheets/sheet1.xml"].SheetViews = &xlsxSheetViews{SheetView: []xlsxSheetView{}}
+	f.SetActiveSheet(1)
+	f.Sheet["xl/worksheets/sheet1.xml"].SheetViews = nil
+	f.SetActiveSheet(1)
+}
+
 func TestSetSheetVisible(t *testing.T) {
 	f := NewFile()
 	f.WorkBook.Sheets.Sheet[0].Name = "SheetN"
@@ -970,7 +957,7 @@ func TestSetSheetVisible(t *testing.T) {
 func TestGetActiveSheetIndex(t *testing.T) {
 	f := NewFile()
 	f.WorkBook.BookViews = nil
-	assert.Equal(t, 0, f.GetActiveSheetIndex())
+	assert.Equal(t, 1, f.GetActiveSheetIndex())
 }
 
 func TestRelsWriter(t *testing.T) {
@@ -987,7 +974,7 @@ func TestGetSheetView(t *testing.T) {
 
 func TestConditionalFormat(t *testing.T) {
 	f := NewFile()
-	sheet1 := f.GetSheetName(0)
+	sheet1 := f.GetSheetName(1)
 
 	fillCells(f, sheet1, 10, 15)
 
@@ -1073,7 +1060,7 @@ func TestConditionalFormat(t *testing.T) {
 
 func TestConditionalFormatError(t *testing.T) {
 	f := NewFile()
-	sheet1 := f.GetSheetName(0)
+	sheet1 := f.GetSheetName(1)
 
 	fillCells(f, sheet1, 10, 15)
 
@@ -1117,33 +1104,26 @@ func TestSetSheetRow(t *testing.T) {
 	assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetSheetRow.xlsx")))
 }
 
+func TestThemeColor(t *testing.T) {
+	t.Log(ThemeColor("000000", -0.1))
+	t.Log(ThemeColor("000000", 0))
+	t.Log(ThemeColor("000000", 1))
+}
+
 func TestHSL(t *testing.T) {
 	var hsl HSL
-	r, g, b, a := hsl.RGBA()
-	assert.Equal(t, uint32(0), r)
-	assert.Equal(t, uint32(0), g)
-	assert.Equal(t, uint32(0), b)
-	assert.Equal(t, uint32(0xffff), a)
-	assert.Equal(t, HSL{0, 0, 0}, hslModel(hsl))
-	assert.Equal(t, HSL{0, 0, 0}, hslModel(color.Gray16{Y: uint16(1)}))
-	R, G, B := HSLToRGB(0, 1, 0.4)
-	assert.Equal(t, uint8(204), R)
-	assert.Equal(t, uint8(0), G)
-	assert.Equal(t, uint8(0), B)
-	R, G, B = HSLToRGB(0, 1, 0.6)
-	assert.Equal(t, uint8(255), R)
-	assert.Equal(t, uint8(51), G)
-	assert.Equal(t, uint8(51), B)
-	assert.Equal(t, 0.0, hueToRGB(0, 0, -1))
-	assert.Equal(t, 0.0, hueToRGB(0, 0, 2))
-	assert.Equal(t, 0.0, hueToRGB(0, 0, 1.0/7))
-	assert.Equal(t, 0.0, hueToRGB(0, 0, 0.4))
-	assert.Equal(t, 0.0, hueToRGB(0, 0, 2.0/4))
+	t.Log(hsl.RGBA())
+	t.Log(hslModel(hsl))
+	t.Log(hslModel(color.Gray16{Y: uint16(1)}))
+	t.Log(HSLToRGB(0, 1, 0.4))
+	t.Log(HSLToRGB(0, 1, 0.6))
+	t.Log(hueToRGB(0, 0, -1))
+	t.Log(hueToRGB(0, 0, 2))
+	t.Log(hueToRGB(0, 0, 1.0/7))
+	t.Log(hueToRGB(0, 0, 0.4))
+	t.Log(hueToRGB(0, 0, 2.0/4))
 	t.Log(RGBToHSL(255, 255, 0))
-	h, s, l := RGBToHSL(0, 255, 255)
-	assert.Equal(t, float64(0.5), h)
-	assert.Equal(t, float64(1), s)
-	assert.Equal(t, float64(0.5), l)
+	t.Log(RGBToHSL(0, 255, 255))
 	t.Log(RGBToHSL(250, 100, 50))
 	t.Log(RGBToHSL(50, 100, 250))
 	t.Log(RGBToHSL(250, 50, 100))
@@ -1178,16 +1158,13 @@ func TestSetDefaultTimeStyle(t *testing.T) {
 	f := NewFile()
 	// Test set default time style on not exists worksheet.
 	assert.EqualError(t, f.setDefaultTimeStyle("SheetN", "", 0), "sheet SheetN is not exist")
-
-	// Test set default time style on invalid cell
-	assert.EqualError(t, f.setDefaultTimeStyle("Sheet1", "", 42), "cannot convert cell \"\" to coordinates: invalid cell name \"\"")
 }
 
 func TestAddVBAProject(t *testing.T) {
 	f := NewFile()
 	assert.NoError(t, f.SetSheetPrOptions("Sheet1", CodeName("Sheet1")))
 	assert.EqualError(t, f.AddVBAProject("macros.bin"), "stat macros.bin: no such file or directory")
-	assert.EqualError(t, f.AddVBAProject(filepath.Join("test", "Book1.xlsx")), ErrAddVBAProject.Error())
+	assert.EqualError(t, f.AddVBAProject(filepath.Join("test", "Book1.xlsx")), "unsupported VBA project extension")
 	assert.NoError(t, f.AddVBAProject(filepath.Join("test", "vbaProject.bin")))
 	// Test add VBA project twice.
 	assert.NoError(t, f.AddVBAProject(filepath.Join("test", "vbaProject.bin")))
@@ -1195,7 +1172,7 @@ func TestAddVBAProject(t *testing.T) {
 }
 
 func TestContentTypesReader(t *testing.T) {
-	// Test unsupported charset.
+	// Test unsupport charset.
 	f := NewFile()
 	f.ContentTypes = nil
 	f.XLSX["[Content_Types].xml"] = MacintoshCyrillicCharset
@@ -1203,7 +1180,7 @@ func TestContentTypesReader(t *testing.T) {
 }
 
 func TestWorkbookReader(t *testing.T) {
-	// Test unsupported charset.
+	// Test unsupport charset.
 	f := NewFile()
 	f.WorkBook = nil
 	f.XLSX["xl/workbook.xml"] = MacintoshCyrillicCharset
@@ -1211,7 +1188,7 @@ func TestWorkbookReader(t *testing.T) {
 }
 
 func TestWorkSheetReader(t *testing.T) {
-	// Test unsupported charset.
+	// Test unsupport charset.
 	f := NewFile()
 	delete(f.Sheet, "xl/worksheets/sheet1.xml")
 	f.XLSX["xl/worksheets/sheet1.xml"] = MacintoshCyrillicCharset
@@ -1228,7 +1205,7 @@ func TestWorkSheetReader(t *testing.T) {
 }
 
 func TestRelsReader(t *testing.T) {
-	// Test unsupported charset.
+	// Test unsupport charset.
 	f := NewFile()
 	rels := "xl/_rels/workbook.xml.rels"
 	f.Relationships[rels] = nil

+ 8 - 53
file.go

@@ -1,13 +1,11 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
+// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
 // this source code is governed by a BSD-style license that can be found in
 // the LICENSE file.
 //
 // Package excelize providing a set of functions that allow you to write to
-// and read from XLSX / XLSM / XLTM files. Supports reading and writing
-// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
-// complex components by high compatibility, and provided streaming API for
-// generating or reading data from a worksheet with huge amounts of data. This
-// library needs Go version 1.15 or later.
+// and read from XLSX files. Support reads and writes XLSX file generated by
+// Microsoft Excel™ 2007 and later. Support save file without losing original
+// charts of XLSX. This library needs Go version 1.10 or later.
 
 package excelize
 
@@ -22,7 +20,7 @@ import (
 // NewFile provides a function to create new file by default template. For
 // example:
 //
-//    f := NewFile()
+//    xlsx := NewFile()
 //
 func NewFile() *File {
 	file := make(map[string][]byte)
@@ -53,7 +51,7 @@ func NewFile() *File {
 	return f
 }
 
-// Save provides a function to override the spreadsheet with origin path.
+// Save provides a function to override the xlsx file with origin path.
 func (f *File) Save() error {
 	if f.Path == "" {
 		return fmt.Errorf("no path defined for file, consider File.WriteTo or File.Write")
@@ -61,21 +59,14 @@ func (f *File) Save() error {
 	return f.SaveAs(f.Path)
 }
 
-// SaveAs provides a function to create or update to an spreadsheet at the
+// SaveAs provides a function to create or update to an xlsx file at the
 // provided path.
-func (f *File) SaveAs(name string, opt ...Options) error {
-	if len(name) > MaxFileNameLength {
-		return ErrMaxFileNameLength
-	}
+func (f *File) SaveAs(name string) error {
 	file, err := os.OpenFile(name, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0666)
 	if err != nil {
 		return err
 	}
 	defer file.Close()
-	f.options = nil
-	for _, o := range opt {
-		f.options = &o
-	}
 	return f.Write(file)
 }
 
@@ -109,30 +100,7 @@ func (f *File) WriteToBuffer() (*bytes.Buffer, error) {
 	f.sharedStringsWriter()
 	f.styleSheetWriter()
 
-	for path, stream := range f.streams {
-		fi, err := zw.Create(path)
-		if err != nil {
-			zw.Close()
-			return buf, err
-		}
-		var from io.Reader
-		from, err = stream.rawData.Reader()
-		if err != nil {
-			stream.rawData.Close()
-			return buf, err
-		}
-		_, err = io.Copy(fi, from)
-		if err != nil {
-			zw.Close()
-			return buf, err
-		}
-		stream.rawData.Close()
-	}
-
 	for path, content := range f.XLSX {
-		if _, ok := f.streams[path]; ok {
-			continue
-		}
 		fi, err := zw.Create(path)
 		if err != nil {
 			zw.Close()
@@ -144,18 +112,5 @@ func (f *File) WriteToBuffer() (*bytes.Buffer, error) {
 			return buf, err
 		}
 	}
-
-	if f.options != nil && f.options.Password != "" {
-		if err := zw.Close(); err != nil {
-			return buf, err
-		}
-		b, err := Encrypt(buf.Bytes(), f.options)
-		if err != nil {
-			return buf, err
-		}
-		buf.Reset()
-		buf.Write(b)
-		return buf, nil
-	}
 	return buf, zw.Close()
 }

+ 3 - 3
file_test.go

@@ -19,12 +19,12 @@ func BenchmarkWrite(b *testing.B) {
 				if err != nil {
 					b.Error(err)
 				}
-				if err := f.SetCellValue("Sheet1", val, s); err != nil {
+				if err := f.SetCellDefault("Sheet1", val, s); err != nil {
 					b.Error(err)
 				}
 			}
 		}
-		// Save spreadsheet by the given path.
+		// Save xlsx file by the given path.
 		err := f.SaveAs("./test.xlsx")
 		if err != nil {
 			b.Error(err)
@@ -35,7 +35,7 @@ func BenchmarkWrite(b *testing.B) {
 func TestWriteTo(t *testing.T) {
 	f := File{}
 	buf := bytes.Buffer{}
-	f.XLSX = make(map[string][]byte)
+	f.XLSX = make(map[string][]byte, 0)
 	f.XLSX["/d/"] = []byte("s")
 	_, err := f.WriteTo(bufio.NewWriter(&buf))
 	assert.EqualError(t, err, "zip: write to directory")

+ 6 - 8
go.mod

@@ -1,14 +1,12 @@
 module github.com/360EntSecGroup-Skylar/excelize/v2
 
-go 1.15
+go 1.12
 
 require (
+	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
-	github.com/richardlehane/mscfb v1.0.3
-	github.com/stretchr/testify v1.6.1
-	github.com/xuri/efp v0.0.0-20210322160811-ab561f5b45e3
-	golang.org/x/crypto v0.0.0-20210415154028-4f45737414dc
-	golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb
-	golang.org/x/net v0.0.0-20210415231046-e915ea6b2b7d
-	golang.org/x/text v0.3.6
+	github.com/stretchr/testify v1.3.0
+	golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a
+	golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553
+	golang.org/x/text v0.3.2 // indirect
 )

+ 13 - 25
go.sum

@@ -1,34 +1,22 @@
 github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/richardlehane/mscfb v1.0.3 h1:rD8TBkYWkObWO0oLDFCbwMeZ4KoalxQy+QgniCj3nKI=
-github.com/richardlehane/mscfb v1.0.3/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk=
-github.com/richardlehane/msoleps v1.0.1 h1:RfrALnSNXzmXLbGct/P2b4xkFz4e8Gmj/0Vj9M9xC1o=
-github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
-github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/xuri/efp v0.0.0-20210322160811-ab561f5b45e3 h1:EpI0bqf/eX9SdZDwlMmahKM+CDBgNbsXMhsN28XrM8o=
-github.com/xuri/efp v0.0.0-20210322160811-ab561f5b45e3/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
-golang.org/x/crypto v0.0.0-20210415154028-4f45737414dc h1:+q90ECDSAQirdykUN6sPEiBXBsp8Csjcca8Oy7bgLTA=
-golang.org/x/crypto v0.0.0-20210415154028-4f45737414dc/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
-golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb h1:fqpd0EBDzlHRCjiphRR5Zo/RSWWQlWv34418dnEixWk=
-golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
-golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20210415231046-e915ea6b2b7d h1:BgJvlyh+UqCUaPlscHJ+PN8GcpfrFdr7NHjd1JL0+Gs=
-golang.org/x/net v0.0.0-20210415231046-e915ea6b2b7d/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
-golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a h1:gHevYm0pO4QUbwy8Dmdr01R5r1BuKtfYqRqF0h/Cbh0=
+golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8=
+golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
-golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

+ 40 - 233
lib.go

@@ -1,46 +1,33 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
+// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
 // this source code is governed by a BSD-style license that can be found in
 // the LICENSE file.
 //
 // Package excelize providing a set of functions that allow you to write to
-// and read from XLSX / XLSM / XLTM files. Supports reading and writing
-// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
-// complex components by high compatibility, and provided streaming API for
-// generating or reading data from a worksheet with huge amounts of data. This
-// library needs Go version 1.15 or later.
+// and read from XLSX files. Support reads and writes XLSX file generated by
+// Microsoft Excel™ 2007 and later. Support save file without losing original
+// charts of XLSX. This library needs Go version 1.10 or later.
 
 package excelize
 
 import (
 	"archive/zip"
 	"bytes"
-	"container/list"
-	"encoding/xml"
 	"fmt"
 	"io"
+	"log"
 	"strconv"
 	"strings"
+	"unsafe"
 )
 
-// ReadZipReader can be used to read the spreadsheet in memory without touching the
+// ReadZipReader can be used to read an XLSX in memory without touching the
 // filesystem.
 func ReadZipReader(r *zip.Reader) (map[string][]byte, int, error) {
-	var err error
-	var docPart = map[string]string{
-		"[content_types].xml":  "[Content_Types].xml",
-		"xl/sharedstrings.xml": "xl/sharedStrings.xml",
-	}
 	fileList := make(map[string][]byte, len(r.File))
 	worksheets := 0
 	for _, v := range r.File {
-		fileName := strings.Replace(v.Name, "\\", "/", -1)
-		if partName, ok := docPart[strings.ToLower(fileName)]; ok {
-			fileName = partName
-		}
-		if fileList[fileName], err = readFile(v); err != nil {
-			return nil, 0, err
-		}
-		if strings.HasPrefix(fileName, "xl/worksheets/sheet") {
+		fileList[v.Name] = readFile(v)
+		if strings.HasPrefix(v.Name, "xl/worksheets/sheet") {
 			worksheets++
 		}
 	}
@@ -52,9 +39,6 @@ func (f *File) readXML(name string) []byte {
 	if content, ok := f.XLSX[name]; ok {
 		return content
 	}
-	if content, ok := f.streams[name]; ok {
-		return content.rawData.buf.Bytes()
-	}
 	return []byte{}
 }
 
@@ -68,16 +52,16 @@ func (f *File) saveFileList(name string, content []byte) {
 }
 
 // Read file content as string in a archive file.
-func readFile(file *zip.File) ([]byte, error) {
+func readFile(file *zip.File) []byte {
 	rc, err := file.Open()
 	if err != nil {
-		return nil, err
+		log.Fatal(err)
 	}
 	dat := make([]byte, 0, file.FileInfo().Size())
 	buff := bytes.NewBuffer(dat)
 	_, _ = io.Copy(buff, rc)
 	rc.Close()
-	return buff.Bytes(), nil
+	return buff.Bytes()
 }
 
 // SplitCellName splits cell name to column name and row number.
@@ -148,9 +132,6 @@ func ColumnNameToNumber(name string) (int, error) {
 		}
 		multi *= 26
 	}
-	if col > TotalColumns {
-		return -1, ErrColumnNumber
-	}
 	return col, nil
 }
 
@@ -165,12 +146,9 @@ func ColumnNumberToName(num int) (string, error) {
 	if num < 1 {
 		return "", fmt.Errorf("incorrect column number %d", num)
 	}
-	if num > TotalColumns {
-		return "", ErrColumnNumber
-	}
 	var col string
 	for num > 0 {
-		col = string(rune((num-1)%26+65)) + col
+		col = string((num-1)%26+65) + col
 		num = (num - 1) / 26
 	}
 	return col, nil
@@ -181,8 +159,8 @@ func ColumnNumberToName(num int) (string, error) {
 //
 // Example:
 //
-//    excelize.CellNameToCoordinates("A1") // returns 1, 1, nil
-//    excelize.CellNameToCoordinates("Z3") // returns 26, 3, nil
+//    CellCoordinates("A1") // returns 1, 1, nil
+//    CellCoordinates("Z3") // returns 26, 3, nil
 //
 func CellNameToCoordinates(cell string) (int, int, error) {
 	const msg = "cannot convert cell %q to coordinates: %v"
@@ -191,11 +169,13 @@ func CellNameToCoordinates(cell string) (int, int, error) {
 	if err != nil {
 		return -1, -1, fmt.Errorf(msg, cell, err)
 	}
-	if row > TotalRows {
-		return -1, -1, fmt.Errorf("row number exceeds maximum limit")
-	}
+
 	col, err := ColumnNameToNumber(colname)
-	return col, row, err
+	if err != nil {
+		return -1, -1, fmt.Errorf(msg, cell, err)
+	}
+
+	return col, row, nil
 }
 
 // CoordinatesToCellName converts [X, Y] coordinates to alpha-numeric cell
@@ -203,21 +183,18 @@ func CellNameToCoordinates(cell string) (int, int, error) {
 //
 // Example:
 //
-//    excelize.CoordinatesToCellName(1, 1) // returns "A1", nil
-//    excelize.CoordinatesToCellName(1, 1, true) // returns "$A$1", nil
+//    CoordinatesToCellName(1, 1) // returns "A1", nil
 //
-func CoordinatesToCellName(col, row int, abs ...bool) (string, error) {
+func CoordinatesToCellName(col, row int) (string, error) {
 	if col < 1 || row < 1 {
 		return "", fmt.Errorf("invalid cell coordinates [%d, %d]", col, row)
 	}
-	sign := ""
-	for _, a := range abs {
-		if a {
-			sign = "$"
-		}
-	}
 	colname, err := ColumnNumberToName(col)
-	return sign + colname + sign + strconv.Itoa(row), err
+	if err != nil {
+		// Error should never happens here.
+		return "", fmt.Errorf("invalid cell coordinates [%d, %d]: %v", col, row, err)
+	}
+	return fmt.Sprintf("%s%d", colname, row), nil
 }
 
 // boolPtr returns a pointer to a bool with the given value.
@@ -253,19 +230,24 @@ func parseFormatSet(formatSet string) []byte {
 // Transitional namespaces.
 func namespaceStrictToTransitional(content []byte) []byte {
 	var namespaceTranslationDic = map[string]string{
-		StrictSourceRelationship:               SourceRelationship.Value,
-		StrictSourceRelationshipOfficeDocument: SourceRelationshipOfficeDocument,
-		StrictSourceRelationshipChart:          SourceRelationshipChart,
-		StrictSourceRelationshipComments:       SourceRelationshipComments,
-		StrictSourceRelationshipImage:          SourceRelationshipImage,
-		StrictNameSpaceSpreadSheet:             NameSpaceSpreadSheet.Value,
+		StrictSourceRelationship:         SourceRelationship,
+		StrictSourceRelationshipChart:    SourceRelationshipChart,
+		StrictSourceRelationshipComments: SourceRelationshipComments,
+		StrictSourceRelationshipImage:    SourceRelationshipImage,
+		StrictNameSpaceSpreadSheet:       NameSpaceSpreadSheet,
 	}
 	for s, n := range namespaceTranslationDic {
-		content = bytesReplace(content, []byte(s), []byte(n), -1)
+		content = bytesReplace(content, stringToBytes(s), stringToBytes(n), -1)
 	}
 	return content
 }
 
+// stringToBytes cast a string to bytes pointer and assign the value of this
+// pointer.
+func stringToBytes(s string) []byte {
+	return *(*[]byte)(unsafe.Pointer(&s))
+}
+
 // bytesReplace replace old bytes with given new.
 func bytesReplace(s, old, new []byte, n int) []byte {
 	if n == 0 {
@@ -323,178 +305,3 @@ func genSheetPasswd(plaintext string) string {
 	password ^= 0xCE4B
 	return strings.ToUpper(strconv.FormatInt(password, 16))
 }
-
-// getRootElement extract root element attributes by given XML decoder.
-func getRootElement(d *xml.Decoder) []xml.Attr {
-	tokenIdx := 0
-	for {
-		token, _ := d.Token()
-		if token == nil {
-			break
-		}
-		switch startElement := token.(type) {
-		case xml.StartElement:
-			tokenIdx++
-			if tokenIdx == 1 {
-				return startElement.Attr
-			}
-		}
-	}
-	return nil
-}
-
-// genXMLNamespace generate serialized XML attributes with a multi namespace
-// by given element attributes.
-func genXMLNamespace(attr []xml.Attr) string {
-	var rootElement string
-	for _, v := range attr {
-		if lastSpace := getXMLNamespace(v.Name.Space, attr); lastSpace != "" {
-			if lastSpace == NameSpaceXML {
-				lastSpace = "xml"
-			}
-			rootElement += fmt.Sprintf("%s:%s=\"%s\" ", lastSpace, v.Name.Local, v.Value)
-			continue
-		}
-		rootElement += fmt.Sprintf("%s=\"%s\" ", v.Name.Local, v.Value)
-	}
-	return strings.TrimSpace(rootElement) + ">"
-}
-
-// getXMLNamespace extract XML namespace from specified element name and attributes.
-func getXMLNamespace(space string, attr []xml.Attr) string {
-	for _, attribute := range attr {
-		if attribute.Value == space {
-			return attribute.Name.Local
-		}
-	}
-	return space
-}
-
-// replaceNameSpaceBytes provides a function to replace the XML root element
-// attribute by the given component part path and XML content.
-func (f *File) replaceNameSpaceBytes(path string, contentMarshal []byte) []byte {
-	var oldXmlns = []byte(`xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">`)
-	var newXmlns = []byte(templateNamespaceIDMap)
-	if attr, ok := f.xmlAttr[path]; ok {
-		newXmlns = []byte(genXMLNamespace(attr))
-	}
-	return bytesReplace(contentMarshal, oldXmlns, newXmlns, -1)
-}
-
-// addNameSpaces provides a function to add a XML attribute by the given
-// component part path.
-func (f *File) addNameSpaces(path string, ns xml.Attr) {
-	exist := false
-	mc := false
-	ignore := -1
-	if attr, ok := f.xmlAttr[path]; ok {
-		for i, attribute := range attr {
-			if attribute.Name.Local == ns.Name.Local && attribute.Name.Space == ns.Name.Space {
-				exist = true
-			}
-			if attribute.Name.Local == "Ignorable" && getXMLNamespace(attribute.Name.Space, attr) == "mc" {
-				ignore = i
-			}
-			if attribute.Name.Local == "mc" && attribute.Name.Space == "xmlns" {
-				mc = true
-			}
-		}
-	}
-	if !exist {
-		f.xmlAttr[path] = append(f.xmlAttr[path], ns)
-		if !mc {
-			f.xmlAttr[path] = append(f.xmlAttr[path], SourceRelationshipCompatibility)
-		}
-		if ignore == -1 {
-			f.xmlAttr[path] = append(f.xmlAttr[path], xml.Attr{
-				Name:  xml.Name{Local: "Ignorable", Space: "mc"},
-				Value: ns.Name.Local,
-			})
-			return
-		}
-		f.setIgnorableNameSpace(path, ignore, ns)
-	}
-}
-
-// setIgnorableNameSpace provides a function to set XML namespace as ignorable
-// by the given attribute.
-func (f *File) setIgnorableNameSpace(path string, index int, ns xml.Attr) {
-	ignorableNS := []string{"c14", "cdr14", "a14", "pic14", "x14", "xdr14", "x14ac", "dsp", "mso14", "dgm14", "x15", "x12ac", "x15ac", "xr", "xr2", "xr3", "xr4", "xr5", "xr6", "xr7", "xr8", "xr9", "xr10", "xr11", "xr12", "xr13", "xr14", "xr15", "x15", "x16", "x16r2", "mo", "mx", "mv", "o", "v"}
-	if inStrSlice(strings.Fields(f.xmlAttr[path][index].Value), ns.Name.Local) == -1 && inStrSlice(ignorableNS, ns.Name.Local) != -1 {
-		f.xmlAttr[path][index].Value = strings.TrimSpace(fmt.Sprintf("%s %s", f.xmlAttr[path][index].Value, ns.Name.Local))
-	}
-}
-
-// addSheetNameSpace add XML attribute for worksheet.
-func (f *File) addSheetNameSpace(sheet string, ns xml.Attr) {
-	name := f.sheetMap[trimSheetName(sheet)]
-	f.addNameSpaces(name, ns)
-}
-
-// isNumeric determines whether an expression is a valid numeric type and get
-// the precision for the numeric.
-func isNumeric(s string) (bool, int) {
-	dot := false
-	p := 0
-	for i, v := range s {
-		if v == '.' {
-			if dot {
-				return false, 0
-			}
-			dot = true
-		} else if v < '0' || v > '9' {
-			if i == 0 && v == '-' {
-				continue
-			}
-			return false, 0
-		} else if dot {
-			p++
-		}
-	}
-	return true, p
-}
-
-// Stack defined an abstract data type that serves as a collection of elements.
-type Stack struct {
-	list *list.List
-}
-
-// NewStack create a new stack.
-func NewStack() *Stack {
-	list := list.New()
-	return &Stack{list}
-}
-
-// Push a value onto the top of the stack.
-func (stack *Stack) Push(value interface{}) {
-	stack.list.PushBack(value)
-}
-
-// Pop the top item of the stack and return it.
-func (stack *Stack) Pop() interface{} {
-	e := stack.list.Back()
-	if e != nil {
-		stack.list.Remove(e)
-		return e.Value
-	}
-	return nil
-}
-
-// Peek view the top item on the stack.
-func (stack *Stack) Peek() interface{} {
-	e := stack.list.Back()
-	if e != nil {
-		return e.Value
-	}
-	return nil
-}
-
-// Len return the number of items in the stack.
-func (stack *Stack) Len() int {
-	return stack.list.Len()
-}
-
-// Empty the stack.
-func (stack *Stack) Empty() bool {
-	return stack.list.Len() == 0
-}

+ 1 - 27
lib_test.go

@@ -1,7 +1,6 @@
 package excelize
 
 import (
-	"encoding/xml"
 	"fmt"
 	"strconv"
 	"strings"
@@ -24,6 +23,7 @@ var validColumns = []struct {
 	{Name: "AZ", Num: 26 + 26},
 	{Name: "ZZ", Num: 26 + 26*26},
 	{Name: "AAA", Num: 26 + 26*26 + 1},
+	{Name: "ZZZ", Num: 26 + 26*26 + 26*26*26},
 }
 
 var invalidColumns = []struct {
@@ -72,8 +72,6 @@ func TestColumnNameToNumber_Error(t *testing.T) {
 			assert.Equalf(t, col.Num, out, msg, col.Name)
 		}
 	}
-	_, err := ColumnNameToNumber("XFE")
-	assert.EqualError(t, err, ErrColumnNumber.Error())
 }
 
 func TestColumnNumberToName_OK(t *testing.T) {
@@ -96,9 +94,6 @@ func TestColumnNumberToName_Error(t *testing.T) {
 	if assert.Error(t, err) {
 		assert.Equal(t, "", out)
 	}
-
-	_, err = ColumnNumberToName(TotalColumns + 1)
-	assert.EqualError(t, err, ErrColumnNumber.Error())
 }
 
 func TestSplitCellName_OK(t *testing.T) {
@@ -177,8 +172,6 @@ func TestCellNameToCoordinates_Error(t *testing.T) {
 			assert.Equalf(t, -1, r, msg, cell)
 		}
 	}
-	_, _, err := CellNameToCoordinates("A1048577")
-	assert.EqualError(t, err, "row number exceeds maximum limit")
 }
 
 func TestCoordinatesToCellName_OK(t *testing.T) {
@@ -215,22 +208,3 @@ func TestBytesReplace(t *testing.T) {
 	s := []byte{0x01}
 	assert.EqualValues(t, s, bytesReplace(s, []byte{}, []byte{}, 0))
 }
-
-func TestSetIgnorableNameSpace(t *testing.T) {
-	f := NewFile()
-	f.xmlAttr["xml_path"] = []xml.Attr{{}}
-	f.setIgnorableNameSpace("xml_path", 0, xml.Attr{Name: xml.Name{Local: "c14"}})
-	assert.EqualValues(t, "c14", f.xmlAttr["xml_path"][0].Value)
-}
-
-func TestStack(t *testing.T) {
-	s := NewStack()
-	assert.Equal(t, s.Peek(), nil)
-	assert.Equal(t, s.Pop(), nil)
-}
-
-func TestGenXMLNamespace(t *testing.T) {
-	assert.Equal(t, genXMLNamespace([]xml.Attr{
-		{Name: xml.Name{Space: NameSpaceXML, Local: "space"}, Value: "preserve"},
-	}), `xml:space="preserve">`)
-}

+ 21 - 28
merge.go

@@ -1,13 +1,11 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
+// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
 // this source code is governed by a BSD-style license that can be found in
 // the LICENSE file.
 //
 // Package excelize providing a set of functions that allow you to write to
-// and read from XLSX / XLSM / XLTM files. Supports reading and writing
-// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
-// complex components by high compatibility, and provided streaming API for
-// generating or reading data from a worksheet with huge amounts of data. This
-// library needs Go version 1.15 or later.
+// and read from XLSX files. Support reads and writes XLSX file generated by
+// Microsoft Excel™ 2007 and later. Support save file without losing original
+// charts of XLSX. This library needs Go version 1.10 or later.
 
 package excelize
 
@@ -47,14 +45,14 @@ func (f *File) MergeCell(sheet, hcell, vcell string) error {
 	hcell, _ = CoordinatesToCellName(rect1[0], rect1[1])
 	vcell, _ = CoordinatesToCellName(rect1[2], rect1[3])
 
-	ws, err := f.workSheetReader(sheet)
+	xlsx, err := f.workSheetReader(sheet)
 	if err != nil {
 		return err
 	}
 	ref := hcell + ":" + vcell
-	if ws.MergeCells != nil {
-		for i := 0; i < len(ws.MergeCells.Cells); i++ {
-			cellData := ws.MergeCells.Cells[i]
+	if xlsx.MergeCells != nil {
+		for i := 0; i < len(xlsx.MergeCells.Cells); i++ {
+			cellData := xlsx.MergeCells.Cells[i]
 			if cellData == nil {
 				continue
 			}
@@ -70,7 +68,7 @@ func (f *File) MergeCell(sheet, hcell, vcell string) error {
 
 			// Delete the merged cells of the overlapping area.
 			if isOverlap(rect1, rect2) {
-				ws.MergeCells.Cells = append(ws.MergeCells.Cells[:i], ws.MergeCells.Cells[i+1:]...)
+				xlsx.MergeCells.Cells = append(xlsx.MergeCells.Cells[:i], xlsx.MergeCells.Cells[i+1:]...)
 				i--
 
 				if rect1[0] > rect2[0] {
@@ -93,11 +91,10 @@ func (f *File) MergeCell(sheet, hcell, vcell string) error {
 				ref = hcell + ":" + vcell
 			}
 		}
-		ws.MergeCells.Cells = append(ws.MergeCells.Cells, &xlsxMergeCell{Ref: ref})
+		xlsx.MergeCells.Cells = append(xlsx.MergeCells.Cells, &xlsxMergeCell{Ref: ref})
 	} else {
-		ws.MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: ref}}}
+		xlsx.MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: ref}}}
 	}
-	ws.MergeCells.Count = len(ws.MergeCells.Cells)
 	return err
 }
 
@@ -108,7 +105,7 @@ func (f *File) MergeCell(sheet, hcell, vcell string) error {
 //
 // Attention: overlapped areas will also be unmerged.
 func (f *File) UnmergeCell(sheet string, hcell, vcell string) error {
-	ws, err := f.workSheetReader(sheet)
+	xlsx, err := f.workSheetReader(sheet)
 	if err != nil {
 		return err
 	}
@@ -121,12 +118,12 @@ func (f *File) UnmergeCell(sheet string, hcell, vcell string) error {
 	_ = sortCoordinates(rect1)
 
 	// return nil since no MergeCells in the sheet
-	if ws.MergeCells == nil {
+	if xlsx.MergeCells == nil {
 		return nil
 	}
 
 	i := 0
-	for _, cellData := range ws.MergeCells.Cells {
+	for _, cellData := range xlsx.MergeCells.Cells {
 		if cellData == nil {
 			continue
 		}
@@ -143,14 +140,10 @@ func (f *File) UnmergeCell(sheet string, hcell, vcell string) error {
 		if isOverlap(rect1, rect2) {
 			continue
 		}
-		ws.MergeCells.Cells[i] = cellData
+		xlsx.MergeCells.Cells[i] = cellData
 		i++
 	}
-	ws.MergeCells.Cells = ws.MergeCells.Cells[:i]
-	ws.MergeCells.Count = len(ws.MergeCells.Cells)
-	if ws.MergeCells.Count == 0 {
-		ws.MergeCells = nil
-	}
+	xlsx.MergeCells.Cells = xlsx.MergeCells.Cells[:i]
 	return nil
 }
 
@@ -158,15 +151,15 @@ func (f *File) UnmergeCell(sheet string, hcell, vcell string) error {
 // currently.
 func (f *File) GetMergeCells(sheet string) ([]MergeCell, error) {
 	var mergeCells []MergeCell
-	ws, err := f.workSheetReader(sheet)
+	xlsx, err := f.workSheetReader(sheet)
 	if err != nil {
 		return mergeCells, err
 	}
-	if ws.MergeCells != nil {
-		mergeCells = make([]MergeCell, 0, len(ws.MergeCells.Cells))
+	if xlsx.MergeCells != nil {
+		mergeCells = make([]MergeCell, 0, len(xlsx.MergeCells.Cells))
 
-		for i := range ws.MergeCells.Cells {
-			ref := ws.MergeCells.Cells[i].Ref
+		for i := range xlsx.MergeCells.Cells {
+			ref := xlsx.MergeCells.Cells[i].Ref
 			axis := strings.Split(ref, ":")[0]
 			val, _ := f.GetCellValue(sheet, axis)
 			mergeCells = append(mergeCells, []string{ref, val})

+ 2 - 2
merge_test.go

@@ -108,7 +108,7 @@ func TestGetMergeCells(t *testing.T) {
 	if !assert.NoError(t, err) {
 		t.FailNow()
 	}
-	sheet1 := f.GetSheetName(0)
+	sheet1 := f.GetSheetName(1)
 
 	mergeCells, err := f.GetMergeCells(sheet1)
 	if !assert.Len(t, mergeCells, len(wants)) {
@@ -132,7 +132,7 @@ func TestUnmergeCell(t *testing.T) {
 	if !assert.NoError(t, err) {
 		t.FailNow()
 	}
-	sheet1 := f.GetSheetName(0)
+	sheet1 := f.GetSheetName(1)
 
 	xlsx, err := f.workSheetReader(sheet1)
 	assert.NoError(t, err)

+ 26 - 85
picture.go

@@ -1,13 +1,11 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
+// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
 // this source code is governed by a BSD-style license that can be found in
 // the LICENSE file.
 //
 // Package excelize providing a set of functions that allow you to write to
-// and read from XLSX / XLSM / XLTM files. Supports reading and writing
-// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
-// complex components by high compatibility, and provided streaming API for
-// generating or reading data from a worksheet with huge amounts of data. This
-// library needs Go version 1.15 or later.
+// and read from XLSX files. Support reads and writes XLSX file generated by
+// Microsoft Excel™ 2007 and later. Support save file without losing original
+// charts of XLSX. This library needs Go version 1.10 or later.
 
 package excelize
 
@@ -15,6 +13,7 @@ import (
 	"bytes"
 	"encoding/json"
 	"encoding/xml"
+	"errors"
 	"fmt"
 	"image"
 	"io"
@@ -33,7 +32,6 @@ func parseFormatPictureSet(formatSet string) (*formatPicture, error) {
 		FPrintsWithSheet: true,
 		FLocksWithSheet:  false,
 		NoChangeAspect:   false,
-		Autofit:          false,
 		OffsetX:          0,
 		OffsetY:          0,
 		XScale:           1.0,
@@ -54,7 +52,7 @@ func parseFormatPictureSet(formatSet string) (*formatPicture, error) {
 //        _ "image/jpeg"
 //        _ "image/png"
 //
-//        "github.com/360EntSecGroup-Skylar/excelize/v2"
+//        "github.com/360EntSecGroup-Skylar/excelize"
 //    )
 //
 //    func main() {
@@ -92,7 +90,7 @@ func (f *File) AddPicture(sheet, cell, picture, format string) error {
 	}
 	ext, ok := supportImageTypes[path.Ext(picture)]
 	if !ok {
-		return ErrImgExt
+		return errors.New("unsupported image extension")
 	}
 	file, _ := ioutil.ReadFile(picture)
 	_, name := filepath.Split(picture)
@@ -110,7 +108,7 @@ func (f *File) AddPicture(sheet, cell, picture, format string) error {
 //        _ "image/jpeg"
 //        "io/ioutil"
 //
-//        "github.com/360EntSecGroup-Skylar/excelize/v2"
+//        "github.com/360EntSecGroup-Skylar/excelize"
 //    )
 //
 //    func main() {
@@ -133,7 +131,7 @@ func (f *File) AddPictureFromBytes(sheet, cell, format, name, extension string,
 	var hyperlinkType string
 	ext, ok := supportImageTypes[extension]
 	if !ok {
-		return ErrImgExt
+		return errors.New("unsupported image extension")
 	}
 	formatSet, err := parseFormatPictureSet(format)
 	if err != nil {
@@ -144,14 +142,14 @@ func (f *File) AddPictureFromBytes(sheet, cell, format, name, extension string,
 		return err
 	}
 	// Read sheet data.
-	ws, err := f.workSheetReader(sheet)
+	xlsx, err := f.workSheetReader(sheet)
 	if err != nil {
 		return err
 	}
 	// Add first picture for given sheet, create xl/drawings/ and xl/drawings/_rels/ folder.
 	drawingID := f.countDrawings() + 1
 	drawingXML := "xl/drawings/drawing" + strconv.Itoa(drawingID) + ".xml"
-	drawingID, drawingXML = f.prepareDrawing(ws, drawingID, sheet, drawingXML)
+	drawingID, drawingXML = f.prepareDrawing(xlsx, drawingID, sheet, drawingXML)
 	drawingRels := "xl/drawings/_rels/drawing" + strconv.Itoa(drawingID) + ".xml.rels"
 	mediaStr := ".." + strings.TrimPrefix(f.addMedia(file, ext), "xl")
 	drawingRID := f.addRels(drawingRels, SourceRelationshipImage, mediaStr, hyperlinkType)
@@ -167,7 +165,6 @@ func (f *File) AddPictureFromBytes(sheet, cell, format, name, extension string,
 		return err
 	}
 	f.addContentTypePart(drawingID, "drawings")
-	f.addSheetNameSpace(sheet, SourceRelationship)
 	return err
 }
 
@@ -247,18 +244,11 @@ func (f *File) addDrawingPicture(sheet, drawingXML, cell, file string, width, he
 	if err != nil {
 		return err
 	}
-	if formatSet.Autofit {
-		width, height, col, row, err = f.drawingResize(sheet, cell, float64(width), float64(height), formatSet)
-		if err != nil {
-			return err
-		}
-	} else {
-		width = int(float64(width) * formatSet.XScale)
-		height = int(float64(height) * formatSet.YScale)
-	}
+	width = int(float64(width) * formatSet.XScale)
+	height = int(float64(height) * formatSet.YScale)
 	col--
 	row--
-	colStart, rowStart, colEnd, rowEnd, x2, y2 :=
+	colStart, rowStart, _, _, colEnd, rowEnd, x2, y2 :=
 		f.positionObjectPixels(sheet, col, row, formatSet.OffsetX, formatSet.OffsetY, width, height)
 	content, cNvPrID := f.drawingParser(drawingXML)
 	twoCellAnchor := xdrCellAnchor{}
@@ -282,11 +272,11 @@ func (f *File) addDrawingPicture(sheet, drawingXML, cell, file string, width, he
 	pic.NvPicPr.CNvPr.Name = "Picture " + strconv.Itoa(cNvPrID)
 	if hyperlinkRID != 0 {
 		pic.NvPicPr.CNvPr.HlinkClick = &xlsxHlinkClick{
-			R:   SourceRelationship.Value,
+			R:   SourceRelationship,
 			RID: "rId" + strconv.Itoa(hyperlinkRID),
 		}
 	}
-	pic.BlipFill.Blip.R = SourceRelationship.Value
+	pic.BlipFill.Blip.R = SourceRelationship
 	pic.BlipFill.Blip.Embed = "rId" + strconv.Itoa(rID)
 	pic.SpPr.PrstGeom.Prst = "rect"
 
@@ -458,14 +448,14 @@ func (f *File) GetPicture(sheet, cell string) (string, []byte, error) {
 	}
 	col--
 	row--
-	ws, err := f.workSheetReader(sheet)
+	xlsx, err := f.workSheetReader(sheet)
 	if err != nil {
 		return "", nil, err
 	}
-	if ws.Drawing == nil {
+	if xlsx.Drawing == nil {
 		return "", nil, err
 	}
-	target := f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID)
+	target := f.getSheetRelationshipsTargetByID(sheet, xlsx.Drawing.RID)
 	drawingXML := strings.Replace(target, "..", "xl", -1)
 	_, ok := f.XLSX[drawingXML]
 	if !ok {
@@ -477,7 +467,7 @@ func (f *File) GetPicture(sheet, cell string) (string, []byte, error) {
 	return f.getPicture(row, col, drawingXML, drawingRelationships)
 }
 
-// DeletePicture provides a function to delete charts in spreadsheet by given
+// DeletePicture provides a function to delete charts in XLSX by given
 // worksheet and cell name. Note that the image file won't be deleted from the
 // document currently.
 func (f *File) DeletePicture(sheet, cell string) (err error) {
@@ -499,7 +489,7 @@ func (f *File) DeletePicture(sheet, cell string) (err error) {
 }
 
 // getPicture provides a function to get picture base name and raw content
-// embed in spreadsheet by given coordinates and drawing relationships.
+// embed in XLSX by given coordinates and drawing relationships.
 func (f *File) getPicture(row, col int, drawingXML, drawingRelationships string) (ret string, buf []byte, err error) {
 	var (
 		wsDr            *xlsxWsDr
@@ -552,12 +542,11 @@ func (f *File) getPictureFromWsDr(row, col int, drawingRelationships string, wsD
 	for _, anchor = range wsDr.TwoCellAnchor {
 		if anchor.From != nil && anchor.Pic != nil {
 			if anchor.From.Col == col && anchor.From.Row == row {
-				if drawRel = f.getDrawingRelationships(drawingRelationships,
-					anchor.Pic.BlipFill.Blip.Embed); drawRel != nil {
-					if _, ok = supportImageTypes[filepath.Ext(drawRel.Target)]; ok {
-						ret, buf = filepath.Base(drawRel.Target), f.XLSX[strings.Replace(drawRel.Target, "..", "xl", -1)]
-						return
-					}
+				drawRel = f.getDrawingRelationships(drawingRelationships,
+					anchor.Pic.BlipFill.Blip.Embed)
+				if _, ok = supportImageTypes[filepath.Ext(drawRel.Target)]; ok {
+					ret, buf = filepath.Base(drawRel.Target), f.XLSX[strings.Replace(drawRel.Target, "..", "xl", -1)]
+					return
 				}
 			}
 		}
@@ -589,51 +578,3 @@ func (f *File) drawingsWriter() {
 		}
 	}
 }
-
-// drawingResize calculate the height and width after resizing.
-func (f *File) drawingResize(sheet string, cell string, width, height float64, formatSet *formatPicture) (w, h, c, r int, err error) {
-	var mergeCells []MergeCell
-	mergeCells, err = f.GetMergeCells(sheet)
-	if err != nil {
-		return
-	}
-	var rng []int
-	var inMergeCell bool
-	if c, r, err = CellNameToCoordinates(cell); err != nil {
-		return
-	}
-	cellWidth, cellHeight := f.getColWidth(sheet, c), f.getRowHeight(sheet, r)
-	for _, mergeCell := range mergeCells {
-		if inMergeCell {
-			continue
-		}
-		if inMergeCell, err = f.checkCellInArea(cell, mergeCell[0]); err != nil {
-			return
-		}
-		if inMergeCell {
-			rng, _ = areaRangeToCoordinates(mergeCell.GetStartAxis(), mergeCell.GetEndAxis())
-			_ = sortCoordinates(rng)
-		}
-	}
-	if inMergeCell {
-		cellWidth, cellHeight = 0, 0
-		c, r = rng[0], rng[1]
-		for col := rng[0] - 1; col < rng[2]; col++ {
-			cellWidth += f.getColWidth(sheet, col)
-		}
-		for row := rng[1] - 1; row < rng[3]; row++ {
-			cellHeight += f.getRowHeight(sheet, row)
-		}
-	}
-	if float64(cellWidth) < width {
-		asp := float64(cellWidth) / width
-		width, height = float64(cellWidth), height*asp
-	}
-	if float64(cellHeight) < height {
-		asp := float64(cellHeight) / height
-		height, width = float64(cellHeight), width*asp
-	}
-	width, height = width-float64(formatSet.OffsetX), height-float64(formatSet.OffsetY)
-	w, h = int(width*formatSet.XScale), int(height*formatSet.YScale)
-	return
-}

+ 3 - 30
picture_test.go

@@ -47,16 +47,6 @@ func TestAddPicture(t *testing.T) {
 	file, err := ioutil.ReadFile(filepath.Join("test", "images", "excel.png"))
 	assert.NoError(t, err)
 
-	// Test add picture to worksheet with autofit.
-	assert.NoError(t, f.AddPicture("Sheet1", "A30", filepath.Join("test", "images", "excel.jpg"), `{"autofit": true}`))
-	assert.NoError(t, f.AddPicture("Sheet1", "B30", filepath.Join("test", "images", "excel.jpg"), `{"x_offset": 10, "y_offset": 10, "autofit": true}`))
-	f.NewSheet("AddPicture")
-	assert.NoError(t, f.SetRowHeight("AddPicture", 10, 30))
-	assert.NoError(t, f.MergeCell("AddPicture", "B3", "D9"))
-	assert.NoError(t, f.MergeCell("AddPicture", "B1", "D1"))
-	assert.NoError(t, f.AddPicture("AddPicture", "C6", filepath.Join("test", "images", "excel.jpg"), `{"autofit": true}`))
-	assert.NoError(t, f.AddPicture("AddPicture", "A1", filepath.Join("test", "images", "excel.jpg"), `{"autofit": true}`))
-
 	// Test add picture to worksheet from bytes.
 	assert.NoError(t, f.AddPictureFromBytes("Sheet1", "Q1", "", "Excel Logo", ".png", file))
 	// Test add picture to worksheet from bytes with illegal cell coordinates.
@@ -80,12 +70,12 @@ func TestAddPictureErrors(t *testing.T) {
 		assert.True(t, os.IsNotExist(err), "Expected os.IsNotExist(err) == true")
 	}
 
-	// Test add picture to worksheet with unsupported file type.
+	// Test add picture to worksheet with unsupport file type.
 	err = xlsx.AddPicture("Sheet1", "G21", filepath.Join("test", "Book1.xlsx"), "")
-	assert.EqualError(t, err, ErrImgExt.Error())
+	assert.EqualError(t, err, "unsupported image extension")
 
 	err = xlsx.AddPictureFromBytes("Sheet1", "G21", "", "Excel Logo", "jpg", make([]byte, 1))
-	assert.EqualError(t, err, ErrImgExt.Error())
+	assert.EqualError(t, err, "unsupported image extension")
 
 	// Test add picture to worksheet with invalid file data.
 	err = xlsx.AddPictureFromBytes("Sheet1", "G21", "", "Excel Logo", ".jpg", make([]byte, 1))
@@ -153,11 +143,6 @@ func TestGetPicture(t *testing.T) {
 	assert.NoError(t, err)
 	assert.Empty(t, file)
 	assert.Empty(t, raw)
-	f, err = prepareTestBook1()
-	assert.NoError(t, err)
-	f.XLSX["xl/drawings/drawing1.xml"] = MacintoshCyrillicCharset
-	_, _, err = f.getPicture(20, 5, "xl/drawings/drawing1.xml", "xl/drawings/_rels/drawing2.xml.rels")
-	assert.EqualError(t, err, "xml decode error: XML syntax error on line 1: invalid UTF-8")
 }
 
 func TestAddDrawingPicture(t *testing.T) {
@@ -196,15 +181,3 @@ func TestDeletePicture(t *testing.T) {
 	// Test delete picture on no chart worksheet.
 	assert.NoError(t, NewFile().DeletePicture("Sheet1", "A1"))
 }
-
-func TestDrawingResize(t *testing.T) {
-	f := NewFile()
-	// Test calculate drawing resize on not exists worksheet.
-	_, _, _, _, err := f.drawingResize("SheetN", "A1", 1, 1, nil)
-	assert.EqualError(t, err, "sheet SheetN is not exist")
-	// Test calculate drawing resize with invalid coordinates.
-	_, _, _, _, err = f.drawingResize("Sheet1", "", 1, 1, nil)
-	assert.EqualError(t, err, `cannot convert cell "" to coordinates: invalid cell name ""`)
-	f.Sheet["xl/worksheets/sheet1.xml"].MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:A"}}}
-	assert.EqualError(t, f.AddPicture("Sheet1", "A1", filepath.Join("test", "images", "excel.jpg"), `{"autofit": true}`), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
-}

+ 41 - 139
pivotTable.go

@@ -1,13 +1,11 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
+// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
 // this source code is governed by a BSD-style license that can be found in
 // the LICENSE file.
 //
 // Package excelize providing a set of functions that allow you to write to
-// and read from XLSX / XLSM / XLTM files. Supports reading and writing
-// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
-// complex components by high compatibility, and provided streaming API for
-// generating or reading data from a worksheet with huge amounts of data. This
-// library needs Go version 1.15 or later.
+// and read from XLSX files. Support reads and writes XLSX file generated by
+// Microsoft Excel™ 2007 and later. Support save file without losing original
+// charts of XLSX. This library needs Go version 1.10 or later.
 
 package excelize
 
@@ -21,26 +19,12 @@ import (
 
 // PivotTableOption directly maps the format settings of the pivot table.
 type PivotTableOption struct {
-	DataRange           string
-	PivotTableRange     string
-	Rows                []PivotTableField
-	Columns             []PivotTableField
-	Data                []PivotTableField
-	Filter              []PivotTableField
-	RowGrandTotals      bool
-	ColGrandTotals      bool
-	ShowDrill           bool
-	UseAutoFormatting   bool
-	PageOverThenDown    bool
-	MergeItem           bool
-	CompactData         bool
-	ShowError           bool
-	ShowRowHeaders      bool
-	ShowColHeaders      bool
-	ShowRowStripes      bool
-	ShowColStripes      bool
-	ShowLastColumn      bool
-	PivotTableStyleName string
+	DataRange       string
+	PivotTableRange string
+	Rows            []PivotTableField
+	Columns         []PivotTableField
+	Data            []PivotTableField
+	Filter          []PivotTableField
 }
 
 // PivotTableField directly maps the field settings of the pivot table.
@@ -63,15 +47,13 @@ type PivotTableOption struct {
 // Name specifies the name of the data field. Maximum 255 characters
 // are allowed in data field name, excess characters will be truncated.
 type PivotTableField struct {
-	Data            string
-	Name            string
-	Subtotal        string
-	DefaultSubtotal bool
+	Data     string
+	Name     string
+	Subtotal string
 }
 
 // AddPivotTable provides the method to add pivot table by given pivot table
-// options. Note that the same fields can not in Columns, Rows and Filter
-// fields at the same time.
+// options.
 //
 // For example, create a pivot table on the Sheet1!$G$2:$M$34 area with the
 // region Sheet1!$A$1:$E$31 as the data source, summarize by sum for sales:
@@ -82,7 +64,7 @@ type PivotTableField struct {
 //        "fmt"
 //        "math/rand"
 //
-//        "github.com/360EntSecGroup-Skylar/excelize/v2"
+//        "github.com/360EntSecGroup-Skylar/excelize"
 //    )
 //
 //    func main() {
@@ -103,16 +85,10 @@ type PivotTableField struct {
 //        if err := f.AddPivotTable(&excelize.PivotTableOption{
 //            DataRange:       "Sheet1!$A$1:$E$31",
 //            PivotTableRange: "Sheet1!$G$2:$M$34",
-//            Rows:            []excelize.PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
+//            Rows:            []excelize.PivotTableField{{Data: "Month"}, {Data: "Year"}},
 //            Filter:          []excelize.PivotTableField{{Data: "Region"}},
-//            Columns:         []excelize.PivotTableField{{Data: "Type", DefaultSubtotal: true}},
+//            Columns:         []excelize.PivotTableField{{Data: "Type"}},
 //            Data:            []excelize.PivotTableField{{Data: "Sales", Name: "Summarize", Subtotal: "Sum"}},
-//            RowGrandTotals:  true,
-//            ColGrandTotals:  true,
-//            ShowDrill:       true,
-//            ShowRowHeaders:  true,
-//            ShowColHeaders:  true,
-//            ShowLastColumn:  true,
 //        }); err != nil {
 //            fmt.Println(err)
 //        }
@@ -140,7 +116,7 @@ func (f *File) AddPivotTable(opt *PivotTableOption) error {
 	}
 
 	// workbook pivot cache
-	workBookPivotCacheRID := f.addRels(f.getWorkbookRelsPath(), SourceRelationshipPivotCache, fmt.Sprintf("/xl/pivotCache/pivotCacheDefinition%d.xml", pivotCacheID), "")
+	workBookPivotCacheRID := f.addRels("xl/_rels/workbook.xml.rels", SourceRelationshipPivotCache, fmt.Sprintf("pivotCache/pivotCacheDefinition%d.xml", pivotCacheID), "")
 	cacheID := f.addWorkbookPivotCache(workBookPivotCacheRID)
 
 	pivotCacheRels := "xl/pivotTables/_rels/pivotTable" + strconv.Itoa(pivotTableID) + ".xml.rels"
@@ -244,11 +220,8 @@ func (f *File) addPivotCache(pivotCacheID int, pivotCacheXML string, opt *PivotT
 	hcell, _ := CoordinatesToCellName(coordinates[0], coordinates[1])
 	vcell, _ := CoordinatesToCellName(coordinates[2], coordinates[3])
 	pc := xlsxPivotCacheDefinition{
-		SaveData:              false,
-		RefreshOnLoad:         true,
-		CreatedVersion:        pivotTableVersion,
-		RefreshedVersion:      pivotTableVersion,
-		MinRefreshableVersion: pivotTableVersion,
+		SaveData:      false,
+		RefreshOnLoad: true,
 		CacheSource: &xlsxCacheSource{
 			Type: "worksheet",
 			WorksheetSource: &xlsxWorksheetSource{
@@ -258,25 +231,12 @@ func (f *File) addPivotCache(pivotCacheID int, pivotCacheXML string, opt *PivotT
 		},
 		CacheFields: &xlsxCacheFields{},
 	}
-
 	for _, name := range order {
-		defaultRowsSubtotal, rowOk := f.getPivotTableFieldNameDefaultSubtotal(name, opt.Rows)
-		defaultColumnsSubtotal, colOk := f.getPivotTableFieldNameDefaultSubtotal(name, opt.Columns)
-		sharedItems := xlsxSharedItems{
-			Count: 0,
-		}
-		s := xlsxString{}
-		if (rowOk && !defaultRowsSubtotal) || (colOk && !defaultColumnsSubtotal) {
-			s = xlsxString{
-				V: "",
-			}
-			sharedItems.Count++
-			sharedItems.S = &s
-		}
-
 		pc.CacheFields.CacheField = append(pc.CacheFields.CacheField, &xlsxCacheField{
-			Name:        name,
-			SharedItems: &sharedItems,
+			Name: name,
+			SharedItems: &xlsxSharedItems{
+				Count: 0,
+			},
 		})
 	}
 	pc.CacheFields.Count = len(pc.CacheFields.CacheField)
@@ -297,27 +257,10 @@ func (f *File) addPivotTable(cacheID, pivotTableID int, pivotTableXML string, op
 	hcell, _ := CoordinatesToCellName(coordinates[0], coordinates[1])
 	vcell, _ := CoordinatesToCellName(coordinates[2], coordinates[3])
 
-	pivotTableStyle := func() string {
-		if opt.PivotTableStyleName == "" {
-			return "PivotStyleLight16"
-		}
-		return opt.PivotTableStyleName
-	}
 	pt := xlsxPivotTableDefinition{
-		Name:                  fmt.Sprintf("Pivot Table%d", pivotTableID),
-		CacheID:               cacheID,
-		RowGrandTotals:        &opt.RowGrandTotals,
-		ColGrandTotals:        &opt.ColGrandTotals,
-		UpdatedVersion:        pivotTableVersion,
-		MinRefreshableVersion: pivotTableVersion,
-		ShowDrill:             &opt.ShowDrill,
-		UseAutoFormatting:     &opt.UseAutoFormatting,
-		PageOverThenDown:      &opt.PageOverThenDown,
-		MergeItem:             &opt.MergeItem,
-		CreatedVersion:        pivotTableVersion,
-		CompactData:           &opt.CompactData,
-		ShowError:             &opt.ShowError,
-		DataCaption:           "Values",
+		Name:        fmt.Sprintf("Pivot Table%d", pivotTableID),
+		CacheID:     cacheID,
+		DataCaption: "Values",
 		Location: &xlsxLocation{
 			Ref:            hcell + ":" + vcell,
 			FirstDataCol:   1,
@@ -338,12 +281,10 @@ func (f *File) addPivotTable(cacheID, pivotTableID int, pivotTableXML string, op
 			I:     []*xlsxI{{}},
 		},
 		PivotTableStyleInfo: &xlsxPivotTableStyleInfo{
-			Name:           pivotTableStyle(),
-			ShowRowHeaders: opt.ShowRowHeaders,
-			ShowColHeaders: opt.ShowColHeaders,
-			ShowRowStripes: opt.ShowRowStripes,
-			ShowColStripes: opt.ShowColStripes,
-			ShowLastColumn: opt.ShowLastColumn,
+			Name:           "PivotStyleLight16",
+			ShowRowHeaders: true,
+			ShowColHeaders: true,
+			ShowLastColumn: true,
 		},
 	}
 
@@ -469,15 +410,6 @@ func inPivotTableField(a []PivotTableField, x string) int {
 // definition and option.
 func (f *File) addPivotColFields(pt *xlsxPivotTableDefinition, opt *PivotTableOption) error {
 	if len(opt.Columns) == 0 {
-		if len(opt.Data) <= 1 {
-			return nil
-		}
-		pt.ColFields = &xlsxColFields{}
-		// in order to create pivot table in case there is no input from Columns
-		pt.ColFields.Count = 1
-		pt.ColFields.Field = append(pt.ColFields.Field, &xlsxField{
-			X: -2,
-		})
 		return nil
 	}
 
@@ -494,13 +426,6 @@ func (f *File) addPivotColFields(pt *xlsxPivotTableDefinition, opt *PivotTableOp
 		})
 	}
 
-	//in order to create pivot in case there is many Columns and Many Datas
-	if len(opt.Data) > 1 {
-		pt.ColFields.Field = append(pt.ColFields.Field, &xlsxField{
-			X: -2,
-		})
-	}
-
 	// count col fields
 	pt.ColFields.Count = len(pt.ColFields.Field)
 	return err
@@ -513,25 +438,17 @@ func (f *File) addPivotFields(pt *xlsxPivotTableDefinition, opt *PivotTableOptio
 	if err != nil {
 		return err
 	}
-	x := 0
 	for _, name := range order {
 		if inPivotTableField(opt.Rows, name) != -1 {
-			defaultSubtotal, ok := f.getPivotTableFieldNameDefaultSubtotal(name, opt.Rows)
-			var items []*xlsxItem
-			if !ok || !defaultSubtotal {
-				items = append(items, &xlsxItem{X: &x})
-			} else {
-				items = append(items, &xlsxItem{T: "default"})
-			}
-
 			pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, &xlsxPivotField{
 				Axis: "axisRow",
 				Name: f.getPivotTableFieldName(name, opt.Rows),
 				Items: &xlsxItems{
-					Count: len(items),
-					Item:  items,
+					Count: 1,
+					Item: []*xlsxItem{
+						{T: "default"},
+					},
 				},
-				DefaultSubtotal: &defaultSubtotal,
 			})
 			continue
 		}
@@ -549,21 +466,15 @@ func (f *File) addPivotFields(pt *xlsxPivotTableDefinition, opt *PivotTableOptio
 			continue
 		}
 		if inPivotTableField(opt.Columns, name) != -1 {
-			defaultSubtotal, ok := f.getPivotTableFieldNameDefaultSubtotal(name, opt.Columns)
-			var items []*xlsxItem
-			if !ok || !defaultSubtotal {
-				items = append(items, &xlsxItem{X: &x})
-			} else {
-				items = append(items, &xlsxItem{T: "default"})
-			}
 			pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, &xlsxPivotField{
 				Axis: "axisCol",
 				Name: f.getPivotTableFieldName(name, opt.Columns),
 				Items: &xlsxItems{
-					Count: len(items),
-					Item:  items,
+					Count: 1,
+					Item: []*xlsxItem{
+						{T: "default"},
+					},
 				},
-				DefaultSubtotal: &defaultSubtotal,
 			})
 			continue
 		}
@@ -624,7 +535,7 @@ func (f *File) getPivotTableFieldsSubtotal(fields []PivotTableField) []string {
 	enums := []string{"average", "count", "countNums", "max", "min", "product", "stdDev", "stdDevp", "sum", "var", "varp"}
 	inEnums := func(enums []string, val string) string {
 		for _, enum := range enums {
-			if strings.EqualFold(enum, val) {
+			if strings.ToLower(enum) == strings.ToLower(val) {
 				return enum
 			}
 		}
@@ -661,16 +572,7 @@ func (f *File) getPivotTableFieldName(name string, fields []PivotTableField) str
 	return ""
 }
 
-func (f *File) getPivotTableFieldNameDefaultSubtotal(name string, fields []PivotTableField) (bool, bool) {
-	for _, field := range fields {
-		if field.Data == name {
-			return field.DefaultSubtotal, true
-		}
-	}
-	return false, false
-}
-
-// addWorkbookPivotCache add the association ID of the pivot cache in workbook.xml.
+// addWorkbookPivotCache add the association ID of the pivot cache in xl/workbook.xml.
 func (f *File) addWorkbookPivotCache(RID int) int {
 	wb := f.workbookReader()
 	if wb.PivotCaches == nil {

+ 32 - 105
pivotTable_test.go

@@ -28,127 +28,54 @@ func TestAddPivotTable(t *testing.T) {
 	assert.NoError(t, f.AddPivotTable(&PivotTableOption{
 		DataRange:       "Sheet1!$A$1:$E$31",
 		PivotTableRange: "Sheet1!$G$2:$M$34",
-		Rows:            []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
+		Rows:            []PivotTableField{{Data: "Month"}, {Data: "Year"}},
 		Filter:          []PivotTableField{{Data: "Region"}},
-		Columns:         []PivotTableField{{Data: "Type", DefaultSubtotal: true}},
+		Columns:         []PivotTableField{{Data: "Type"}},
 		Data:            []PivotTableField{{Data: "Sales", Subtotal: "Sum", Name: "Summarize by Sum"}},
-		RowGrandTotals:  true,
-		ColGrandTotals:  true,
-		ShowDrill:       true,
-		ShowRowHeaders:  true,
-		ShowColHeaders:  true,
-		ShowLastColumn:  true,
-		ShowError:       true,
 	}))
 	// Use different order of coordinate tests
 	assert.NoError(t, f.AddPivotTable(&PivotTableOption{
 		DataRange:       "Sheet1!$A$1:$E$31",
 		PivotTableRange: "Sheet1!$U$34:$O$2",
-		Rows:            []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
-		Columns:         []PivotTableField{{Data: "Type", DefaultSubtotal: true}},
+		Rows:            []PivotTableField{{Data: "Month"}, {Data: "Year"}},
+		Columns:         []PivotTableField{{Data: "Type"}},
 		Data:            []PivotTableField{{Data: "Sales", Subtotal: "Average", Name: "Summarize by Average"}},
-		RowGrandTotals:  true,
-		ColGrandTotals:  true,
-		ShowDrill:       true,
-		ShowRowHeaders:  true,
-		ShowColHeaders:  true,
-		ShowLastColumn:  true,
 	}))
 
 	assert.NoError(t, f.AddPivotTable(&PivotTableOption{
 		DataRange:       "Sheet1!$A$1:$E$31",
 		PivotTableRange: "Sheet1!$W$2:$AC$34",
-		Rows:            []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
+		Rows:            []PivotTableField{{Data: "Month"}, {Data: "Year"}},
 		Columns:         []PivotTableField{{Data: "Region"}},
 		Data:            []PivotTableField{{Data: "Sales", Subtotal: "Count", Name: "Summarize by Count"}},
-		RowGrandTotals:  true,
-		ColGrandTotals:  true,
-		ShowDrill:       true,
-		ShowRowHeaders:  true,
-		ShowColHeaders:  true,
-		ShowLastColumn:  true,
 	}))
 	assert.NoError(t, f.AddPivotTable(&PivotTableOption{
 		DataRange:       "Sheet1!$A$1:$E$31",
 		PivotTableRange: "Sheet1!$G$37:$W$50",
 		Rows:            []PivotTableField{{Data: "Month"}},
-		Columns:         []PivotTableField{{Data: "Region", DefaultSubtotal: true}, {Data: "Year"}},
+		Columns:         []PivotTableField{{Data: "Region"}, {Data: "Year"}},
 		Data:            []PivotTableField{{Data: "Sales", Subtotal: "CountNums", Name: "Summarize by CountNums"}},
-		RowGrandTotals:  true,
-		ColGrandTotals:  true,
-		ShowDrill:       true,
-		ShowRowHeaders:  true,
-		ShowColHeaders:  true,
-		ShowLastColumn:  true,
 	}))
 	assert.NoError(t, f.AddPivotTable(&PivotTableOption{
 		DataRange:       "Sheet1!$A$1:$E$31",
 		PivotTableRange: "Sheet1!$AE$2:$AG$33",
-		Rows:            []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
-		Data:            []PivotTableField{{Data: "Sales", Subtotal: "Max", Name: "Summarize by Max"}, {Data: "Sales", Subtotal: "Average", Name: "Average of Sales"}},
-		RowGrandTotals:  true,
-		ColGrandTotals:  true,
-		ShowDrill:       true,
-		ShowRowHeaders:  true,
-		ShowColHeaders:  true,
-		ShowLastColumn:  true,
-	}))
-	// Create pivot table with empty subtotal field name and specified style
-	assert.NoError(t, f.AddPivotTable(&PivotTableOption{
-		DataRange:           "Sheet1!$A$1:$E$31",
-		PivotTableRange:     "Sheet1!$AJ$2:$AP1$35",
-		Rows:                []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
-		Filter:              []PivotTableField{{Data: "Region"}},
-		Columns:             []PivotTableField{},
-		Data:                []PivotTableField{{Subtotal: "Sum", Name: "Summarize by Sum"}},
-		RowGrandTotals:      true,
-		ColGrandTotals:      true,
-		ShowDrill:           true,
-		ShowRowHeaders:      true,
-		ShowColHeaders:      true,
-		ShowLastColumn:      true,
-		PivotTableStyleName: "PivotStyleLight19",
+		Rows:            []PivotTableField{{Data: "Month"}, {Data: "Year"}},
+		Data:            []PivotTableField{{Data: "Sales", Subtotal: "Max", Name: "Summarize by Max"}},
 	}))
 	f.NewSheet("Sheet2")
 	assert.NoError(t, f.AddPivotTable(&PivotTableOption{
 		DataRange:       "Sheet1!$A$1:$E$31",
 		PivotTableRange: "Sheet2!$A$1:$AR$15",
 		Rows:            []PivotTableField{{Data: "Month"}},
-		Columns:         []PivotTableField{{Data: "Region", DefaultSubtotal: true}, {Data: "Type", DefaultSubtotal: true}, {Data: "Year"}},
+		Columns:         []PivotTableField{{Data: "Region"}, {Data: "Type"}, {Data: "Year"}},
 		Data:            []PivotTableField{{Data: "Sales", Subtotal: "Min", Name: "Summarize by Min"}},
-		RowGrandTotals:  true,
-		ColGrandTotals:  true,
-		ShowDrill:       true,
-		ShowRowHeaders:  true,
-		ShowColHeaders:  true,
-		ShowLastColumn:  true,
 	}))
 	assert.NoError(t, f.AddPivotTable(&PivotTableOption{
 		DataRange:       "Sheet1!$A$1:$E$31",
 		PivotTableRange: "Sheet2!$A$18:$AR$54",
-		Rows:            []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Type"}},
-		Columns:         []PivotTableField{{Data: "Region", DefaultSubtotal: true}, {Data: "Year"}},
+		Rows:            []PivotTableField{{Data: "Month"}, {Data: "Type"}},
+		Columns:         []PivotTableField{{Data: "Region"}, {Data: "Year"}},
 		Data:            []PivotTableField{{Data: "Sales", Subtotal: "Product", Name: "Summarize by Product"}},
-		RowGrandTotals:  true,
-		ColGrandTotals:  true,
-		ShowDrill:       true,
-		ShowRowHeaders:  true,
-		ShowColHeaders:  true,
-		ShowLastColumn:  true,
-	}))
-	//Test Pivot table with many data, many rows, many cols
-	assert.NoError(t, f.AddPivotTable(&PivotTableOption{
-		DataRange:       "Sheet1!$A$1:$E$31",
-		PivotTableRange: "Sheet2!$A$56:$AG$90",
-		Rows:            []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
-		Columns:         []PivotTableField{{Data: "Region", DefaultSubtotal: true}, {Data: "Type"}},
-		Data:            []PivotTableField{{Data: "Sales", Subtotal: "Sum", Name: "Sum of Sales"}, {Data: "Sales", Subtotal: "Average", Name: "Average of Sales"}},
-		RowGrandTotals:  true,
-		ColGrandTotals:  true,
-		ShowDrill:       true,
-		ShowRowHeaders:  true,
-		ShowColHeaders:  true,
-		ShowLastColumn:  true,
 	}))
 
 	// Test empty pivot table options
@@ -157,56 +84,56 @@ func TestAddPivotTable(t *testing.T) {
 	assert.EqualError(t, f.AddPivotTable(&PivotTableOption{
 		DataRange:       "Sheet1!$A$1:$A$1",
 		PivotTableRange: "Sheet1!$U$34:$O$2",
-		Rows:            []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
-		Columns:         []PivotTableField{{Data: "Type", DefaultSubtotal: true}},
+		Rows:            []PivotTableField{{Data: "Month"}, {Data: "Year"}},
+		Columns:         []PivotTableField{{Data: "Type"}},
 		Data:            []PivotTableField{{Data: "Sales"}},
 	}), `parameter 'DataRange' parsing error: parameter is invalid`)
 	// Test the data range of the worksheet that is not declared
 	assert.EqualError(t, f.AddPivotTable(&PivotTableOption{
 		DataRange:       "$A$1:$E$31",
 		PivotTableRange: "Sheet1!$U$34:$O$2",
-		Rows:            []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
-		Columns:         []PivotTableField{{Data: "Type", DefaultSubtotal: true}},
+		Rows:            []PivotTableField{{Data: "Month"}, {Data: "Year"}},
+		Columns:         []PivotTableField{{Data: "Type"}},
 		Data:            []PivotTableField{{Data: "Sales"}},
 	}), `parameter 'DataRange' parsing error: parameter is invalid`)
 	// Test the worksheet declared in the data range does not exist
 	assert.EqualError(t, f.AddPivotTable(&PivotTableOption{
 		DataRange:       "SheetN!$A$1:$E$31",
 		PivotTableRange: "Sheet1!$U$34:$O$2",
-		Rows:            []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
-		Columns:         []PivotTableField{{Data: "Type", DefaultSubtotal: true}},
+		Rows:            []PivotTableField{{Data: "Month"}, {Data: "Year"}},
+		Columns:         []PivotTableField{{Data: "Type"}},
 		Data:            []PivotTableField{{Data: "Sales"}},
 	}), "sheet SheetN is not exist")
 	// Test the pivot table range of the worksheet that is not declared
 	assert.EqualError(t, f.AddPivotTable(&PivotTableOption{
 		DataRange:       "Sheet1!$A$1:$E$31",
 		PivotTableRange: "$U$34:$O$2",
-		Rows:            []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
-		Columns:         []PivotTableField{{Data: "Type", DefaultSubtotal: true}},
+		Rows:            []PivotTableField{{Data: "Month"}, {Data: "Year"}},
+		Columns:         []PivotTableField{{Data: "Type"}},
 		Data:            []PivotTableField{{Data: "Sales"}},
 	}), `parameter 'PivotTableRange' parsing error: parameter is invalid`)
 	// Test the worksheet declared in the pivot table range does not exist
 	assert.EqualError(t, f.AddPivotTable(&PivotTableOption{
 		DataRange:       "Sheet1!$A$1:$E$31",
 		PivotTableRange: "SheetN!$U$34:$O$2",
-		Rows:            []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
-		Columns:         []PivotTableField{{Data: "Type", DefaultSubtotal: true}},
+		Rows:            []PivotTableField{{Data: "Month"}, {Data: "Year"}},
+		Columns:         []PivotTableField{{Data: "Type"}},
 		Data:            []PivotTableField{{Data: "Sales"}},
 	}), "sheet SheetN is not exist")
 	// Test not exists worksheet in data range
 	assert.EqualError(t, f.AddPivotTable(&PivotTableOption{
 		DataRange:       "SheetN!$A$1:$E$31",
 		PivotTableRange: "Sheet1!$U$34:$O$2",
-		Rows:            []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
-		Columns:         []PivotTableField{{Data: "Type", DefaultSubtotal: true}},
+		Rows:            []PivotTableField{{Data: "Month"}, {Data: "Year"}},
+		Columns:         []PivotTableField{{Data: "Type"}},
 		Data:            []PivotTableField{{Data: "Sales"}},
 	}), "sheet SheetN is not exist")
 	// Test invalid row number in data range
 	assert.EqualError(t, f.AddPivotTable(&PivotTableOption{
 		DataRange:       "Sheet1!$A$0:$E$31",
 		PivotTableRange: "Sheet1!$U$34:$O$2",
-		Rows:            []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
-		Columns:         []PivotTableField{{Data: "Type", DefaultSubtotal: true}},
+		Rows:            []PivotTableField{{Data: "Month"}, {Data: "Year"}},
+		Columns:         []PivotTableField{{Data: "Type"}},
 		Data:            []PivotTableField{{Data: "Sales"}},
 	}), `parameter 'DataRange' parsing error: cannot convert cell "A0" to coordinates: invalid cell name "A0"`)
 	assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddPivotTable1.xlsx")))
@@ -214,8 +141,8 @@ func TestAddPivotTable(t *testing.T) {
 	assert.NoError(t, f.AddPivotTable(&PivotTableOption{
 		DataRange:       "Sheet1!$A$1:$E$31",
 		PivotTableRange: "Sheet1!$G$2:$M$34",
-		Rows:            []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
-		Columns:         []PivotTableField{{Data: "Type", DefaultSubtotal: true}},
+		Rows:            []PivotTableField{{Data: "Month"}, {Data: "Year"}},
+		Columns:         []PivotTableField{{Data: "Type"}},
 		Data:            []PivotTableField{{Data: "Sales", Subtotal: "-", Name: strings.Repeat("s", 256)}},
 	}))
 
@@ -231,8 +158,8 @@ func TestAddPivotTable(t *testing.T) {
 	assert.EqualError(t, f.addPivotCache(0, "", &PivotTableOption{
 		DataRange:       "$A$1:$E$31",
 		PivotTableRange: "Sheet1!$U$34:$O$2",
-		Rows:            []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
-		Columns:         []PivotTableField{{Data: "Type", DefaultSubtotal: true}},
+		Rows:            []PivotTableField{{Data: "Month"}, {Data: "Year"}},
+		Columns:         []PivotTableField{{Data: "Type"}},
 		Data:            []PivotTableField{{Data: "Sales"}},
 	}, nil), "parameter 'DataRange' parsing error: parameter is invalid")
 	// Test add pivot table with empty options
@@ -243,8 +170,8 @@ func TestAddPivotTable(t *testing.T) {
 	assert.EqualError(t, f.addPivotFields(nil, &PivotTableOption{
 		DataRange:       "$A$1:$E$31",
 		PivotTableRange: "Sheet1!$U$34:$O$2",
-		Rows:            []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
-		Columns:         []PivotTableField{{Data: "Type", DefaultSubtotal: true}},
+		Rows:            []PivotTableField{{Data: "Month"}, {Data: "Year"}},
+		Columns:         []PivotTableField{{Data: "Type"}},
 		Data:            []PivotTableField{{Data: "Sales"}},
 	}), `parameter 'DataRange' parsing error: parameter is invalid`)
 	// Test get pivot fields index with empty data range
@@ -281,7 +208,7 @@ func TestAddPivotColFields(t *testing.T) {
 	// Test invalid data range
 	assert.EqualError(t, f.addPivotColFields(&xlsxPivotTableDefinition{}, &PivotTableOption{
 		DataRange: "Sheet1!$A$1:$A$1",
-		Columns:   []PivotTableField{{Data: "Type", DefaultSubtotal: true}},
+		Columns:   []PivotTableField{{Data: "Type"}},
 	}), `parameter 'DataRange' parsing error: parameter is invalid`)
 }
 

+ 140 - 227
rows.go

@@ -1,37 +1,38 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
+// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
 // this source code is governed by a BSD-style license that can be found in
 // the LICENSE file.
 //
 // Package excelize providing a set of functions that allow you to write to
-// and read from XLSX / XLSM / XLTM files. Supports reading and writing
-// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
-// complex components by high compatibility, and provided streaming API for
-// generating or reading data from a worksheet with huge amounts of data. This
-// library needs Go version 1.15 or later.
+// and read from XLSX files. Support reads and writes XLSX file generated by
+// Microsoft Excel™ 2007 and later. Support save file without losing original
+// charts of XLSX. This library needs Go version 1.10 or later.
 
 package excelize
 
 import (
 	"bytes"
 	"encoding/xml"
+	"errors"
 	"fmt"
 	"io"
 	"log"
 	"math"
 	"strconv"
-
-	"github.com/mohae/deepcopy"
 )
 
 // GetRows return all the rows in a sheet by given worksheet name (case
 // sensitive). For example:
 //
-//    rows, err := f.GetRows("Sheet1")
+//    rows, err := f.Rows("Sheet1")
 //    if err != nil {
 //        fmt.Println(err)
 //        return
 //    }
-//    for _, row := range rows {
+//    for rows.Next() {
+//        row, err := rows.Columns()
+//        if err != nil {
+//            fmt.Println(err)
+//        }
 //        for _, colCell := range row {
 //            fmt.Print(colCell, "\t")
 //        }
@@ -43,26 +44,26 @@ func (f *File) GetRows(sheet string) ([][]string, error) {
 	if err != nil {
 		return nil, err
 	}
-	results, cur, max := make([][]string, 0, 64), 0, 0
+	results := make([][]string, 0, 64)
 	for rows.Next() {
-		cur++
+		if rows.Error() != nil {
+			break
+		}
 		row, err := rows.Columns()
 		if err != nil {
 			break
 		}
 		results = append(results, row)
-		if len(row) > 0 {
-			max = cur
-		}
 	}
-	return results[:max], nil
+	return results, nil
 }
 
-// Rows defines an iterator to a sheet.
+// Rows defines an iterator to a sheet
 type Rows struct {
 	err                        error
 	curRow, totalRow, stashRow int
 	sheet                      string
+	rows                       []xlsxRow
 	f                          *File
 	decoder                    *xml.Decoder
 }
@@ -73,63 +74,69 @@ func (rows *Rows) Next() bool {
 	return rows.curRow <= rows.totalRow
 }
 
-// Error will return the error when the error occurs.
+// Error will return the error when the find next row element
 func (rows *Rows) Error() error {
 	return rows.err
 }
 
-// Columns return the current row's column values.
+// Columns return the current row's column values
 func (rows *Rows) Columns() ([]string, error) {
-	var rowIterator rowXMLIterator
+	var (
+		err          error
+		inElement    string
+		row, cellCol int
+		columns      []string
+	)
+
 	if rows.stashRow >= rows.curRow {
-		return rowIterator.columns, rowIterator.err
+		return columns, err
 	}
-	rowIterator.rows = rows
-	rowIterator.d = rows.f.sharedStringsReader()
+
+	d := rows.f.sharedStringsReader()
 	for {
 		token, _ := rows.decoder.Token()
 		if token == nil {
 			break
 		}
-		switch xmlElement := token.(type) {
+		switch startElement := token.(type) {
 		case xml.StartElement:
-			rowIterator.inElement = xmlElement.Name.Local
-			if rowIterator.inElement == "row" {
-				rowIterator.row++
-				if rowIterator.attrR, rowIterator.err = attrValToInt("r", xmlElement.Attr); rowIterator.attrR != 0 {
-					rowIterator.row = rowIterator.attrR
-				}
-				if rowIterator.row > rowIterator.rows.curRow {
-					rowIterator.rows.stashRow = rowIterator.row - 1
-					return rowIterator.columns, rowIterator.err
+			inElement = startElement.Name.Local
+			if inElement == "row" {
+				for _, attr := range startElement.Attr {
+					if attr.Name.Local == "r" {
+						row, err = strconv.Atoi(attr.Value)
+						if err != nil {
+							return columns, err
+						}
+						if row > rows.curRow {
+							rows.stashRow = row - 1
+							return columns, err
+						}
+					}
 				}
 			}
-			rowXMLHandler(&rowIterator, &xmlElement)
-			if rowIterator.err != nil {
-				return rowIterator.columns, rowIterator.err
+			if inElement == "c" {
+				colCell := xlsxC{}
+				_ = rows.decoder.DecodeElement(&colCell, &startElement)
+				cellCol, _, err = CellNameToCoordinates(colCell.R)
+				if err != nil {
+					return columns, err
+				}
+				blank := cellCol - len(columns)
+				for i := 1; i < blank; i++ {
+					columns = append(columns, "")
+				}
+				val, _ := colCell.getValueFrom(rows.f, d)
+				columns = append(columns, val)
 			}
 		case xml.EndElement:
-			rowIterator.inElement = xmlElement.Name.Local
-			if rowIterator.row == 0 {
-				rowIterator.row = rowIterator.rows.curRow
-			}
-			if rowIterator.inElement == "row" && rowIterator.row+1 < rowIterator.rows.curRow {
-				return rowIterator.columns, rowIterator.err
-			}
-			if rowIterator.inElement == "sheetData" {
-				return rowIterator.columns, rowIterator.err
+			inElement = startElement.Name.Local
+			if inElement == "row" {
+				return columns, err
 			}
 		}
 	}
-	return rowIterator.columns, rowIterator.err
-}
-
-// appendSpace append blank characters to slice by given length and source slice.
-func appendSpace(l int, s []string) []string {
-	for i := 1; i < l; i++ {
-		s = append(s, "")
-	}
-	return s
+	return columns, err
 }
 
 // ErrSheetNotExist defines an error of sheet is not exist
@@ -141,38 +148,7 @@ func (err ErrSheetNotExist) Error() string {
 	return fmt.Sprintf("sheet %s is not exist", string(err.SheetName))
 }
 
-// rowXMLIterator defined runtime use field for the worksheet row SAX parser.
-type rowXMLIterator struct {
-	err                 error
-	inElement           string
-	attrR, cellCol, row int
-	columns             []string
-	rows                *Rows
-	d                   *xlsxSST
-}
-
-// rowXMLHandler parse the row XML element of the worksheet.
-func rowXMLHandler(rowIterator *rowXMLIterator, xmlElement *xml.StartElement) {
-	rowIterator.err = nil
-	if rowIterator.inElement == "c" {
-		rowIterator.cellCol++
-		colCell := xlsxC{}
-		_ = rowIterator.rows.decoder.DecodeElement(&colCell, xmlElement)
-		if colCell.R != "" {
-			if rowIterator.cellCol, _, rowIterator.err = CellNameToCoordinates(colCell.R); rowIterator.err != nil {
-				return
-			}
-		}
-		blank := rowIterator.cellCol - len(rowIterator.columns)
-		val, _ := colCell.getValueFrom(rowIterator.rows.f, rowIterator.d)
-		if val != "" {
-			rowIterator.columns = append(appendSpace(blank, rowIterator.columns), val)
-		}
-	}
-}
-
-// Rows returns a rows iterator, used for streaming reading data for a
-// worksheet with a large data. For example:
+// Rows return a rows iterator. For example:
 //
 //    rows, err := f.Rows("Sheet1")
 //    if err != nil {
@@ -198,7 +174,7 @@ func (f *File) Rows(sheet string) (*Rows, error) {
 	if f.Sheet[name] != nil {
 		// flush data
 		output, _ := xml.Marshal(f.Sheet[name])
-		f.saveFileList(name, f.replaceNameSpaceBytes(name, output))
+		f.saveFileList(name, replaceRelationshipsNameSpaceBytes(output))
 	}
 	var (
 		err       error
@@ -212,12 +188,11 @@ func (f *File) Rows(sheet string) (*Rows, error) {
 		if token == nil {
 			break
 		}
-		switch xmlElement := token.(type) {
+		switch startElement := token.(type) {
 		case xml.StartElement:
-			inElement = xmlElement.Name.Local
+			inElement = startElement.Name.Local
 			if inElement == "row" {
-				row++
-				for _, attr := range xmlElement.Attr {
+				for _, attr := range startElement.Attr {
 					if attr.Name.Local == "r" {
 						row, err = strconv.Atoi(attr.Value)
 						if err != nil {
@@ -227,16 +202,12 @@ func (f *File) Rows(sheet string) (*Rows, error) {
 				}
 				rows.totalRow = row
 			}
-		case xml.EndElement:
-			if xmlElement.Name.Local == "sheetData" {
-				rows.f = f
-				rows.sheet = name
-				rows.decoder = f.xmlNewDecoder(bytes.NewReader(f.readXML(name)))
-				return &rows, nil
-			}
 		default:
 		}
 	}
+	rows.f = f
+	rows.sheet = name
+	rows.decoder = f.xmlNewDecoder(bytes.NewReader(f.readXML(name)))
 	return &rows, nil
 }
 
@@ -249,28 +220,26 @@ func (f *File) SetRowHeight(sheet string, row int, height float64) error {
 	if row < 1 {
 		return newInvalidRowNumberError(row)
 	}
-	if height > MaxRowHeight {
-		return ErrMaxRowHeight
-	}
-	ws, err := f.workSheetReader(sheet)
+
+	xlsx, err := f.workSheetReader(sheet)
 	if err != nil {
 		return err
 	}
 
-	prepareSheetXML(ws, 0, row)
+	prepareSheetXML(xlsx, 0, row)
 
 	rowIdx := row - 1
-	ws.SheetData.Row[rowIdx].Ht = height
-	ws.SheetData.Row[rowIdx].CustomHeight = true
+	xlsx.SheetData.Row[rowIdx].Ht = height
+	xlsx.SheetData.Row[rowIdx].CustomHeight = true
 	return nil
 }
 
 // getRowHeight provides a function to get row height in pixels by given sheet
 // name and row index.
 func (f *File) getRowHeight(sheet string, row int) int {
-	ws, _ := f.workSheetReader(sheet)
-	for i := range ws.SheetData.Row {
-		v := &ws.SheetData.Row[i]
+	xlsx, _ := f.workSheetReader(sheet)
+	for i := range xlsx.SheetData.Row {
+		v := &xlsx.SheetData.Row[i]
 		if v.R == row+1 && v.Ht != 0 {
 			return int(convertRowHeightToPixels(v.Ht))
 		}
@@ -288,109 +257,68 @@ func (f *File) GetRowHeight(sheet string, row int) (float64, error) {
 	if row < 1 {
 		return defaultRowHeightPixels, newInvalidRowNumberError(row)
 	}
-	var ht = defaultRowHeight
-	ws, err := f.workSheetReader(sheet)
+
+	xlsx, err := f.workSheetReader(sheet)
 	if err != nil {
-		return ht, err
+		return defaultRowHeightPixels, err
 	}
-	if ws.SheetFormatPr != nil && ws.SheetFormatPr.CustomHeight {
-		ht = ws.SheetFormatPr.DefaultRowHeight
+	if row > len(xlsx.SheetData.Row) {
+		return defaultRowHeightPixels, nil // it will be better to use 0, but we take care with BC
 	}
-	if row > len(ws.SheetData.Row) {
-		return ht, nil // it will be better to use 0, but we take care with BC
-	}
-	for _, v := range ws.SheetData.Row {
+	for _, v := range xlsx.SheetData.Row {
 		if v.R == row && v.Ht != 0 {
 			return v.Ht, nil
 		}
 	}
 	// Optimisation for when the row heights haven't changed.
-	return ht, nil
+	return defaultRowHeightPixels, nil
 }
 
 // sharedStringsReader provides a function to get the pointer to the structure
 // after deserialization of xl/sharedStrings.xml.
 func (f *File) sharedStringsReader() *xlsxSST {
 	var err error
-	f.Lock()
-	defer f.Unlock()
-	relPath := f.getWorkbookRelsPath()
+
 	if f.SharedStrings == nil {
 		var sharedStrings xlsxSST
 		ss := f.readXML("xl/sharedStrings.xml")
+		if len(ss) == 0 {
+			ss = f.readXML("xl/SharedStrings.xml")
+		}
 		if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(ss))).
 			Decode(&sharedStrings); err != nil && err != io.EOF {
 			log.Printf("xml decode error: %s", err)
 		}
-		if sharedStrings.Count == 0 {
-			sharedStrings.Count = len(sharedStrings.SI)
-		}
-		if sharedStrings.UniqueCount == 0 {
-			sharedStrings.UniqueCount = sharedStrings.Count
-		}
 		f.SharedStrings = &sharedStrings
-		for i := range sharedStrings.SI {
-			if sharedStrings.SI[i].T != nil {
-				f.sharedStringsMap[sharedStrings.SI[i].T.Val] = i
-			}
-		}
-		f.addContentTypePart(0, "sharedStrings")
-		rels := f.relsReader(relPath)
-		for _, rel := range rels.Relationships {
-			if rel.Target == "/xl/sharedStrings.xml" {
-				return f.SharedStrings
-			}
-		}
-		// Update workbook.xml.rels
-		f.addRels(relPath, SourceRelationshipSharedStrings, "/xl/sharedStrings.xml", "")
 	}
 
 	return f.SharedStrings
 }
 
 // getValueFrom return a value from a column/row cell, this function is
-// inteded to be used with for range on rows an argument with the spreadsheet
-// opened file.
-func (c *xlsxC) getValueFrom(f *File, d *xlsxSST) (string, error) {
-	f.Lock()
-	defer f.Unlock()
-	switch c.T {
+// inteded to be used with for range on rows an argument with the xlsx opened
+// file.
+func (xlsx *xlsxC) getValueFrom(f *File, d *xlsxSST) (string, error) {
+	switch xlsx.T {
 	case "s":
-		if c.V != "" {
+		if xlsx.V != "" {
 			xlsxSI := 0
-			xlsxSI, _ = strconv.Atoi(c.V)
+			xlsxSI, _ = strconv.Atoi(xlsx.V)
 			if len(d.SI) > xlsxSI {
-				return f.formattedValue(c.S, d.SI[xlsxSI].String()), nil
+				return f.formattedValue(xlsx.S, d.SI[xlsxSI].String()), nil
 			}
 		}
-		return f.formattedValue(c.S, c.V), nil
+		return f.formattedValue(xlsx.S, xlsx.V), nil
 	case "str":
-		return f.formattedValue(c.S, c.V), nil
+		return f.formattedValue(xlsx.S, xlsx.V), nil
 	case "inlineStr":
-		if c.IS != nil {
-			return f.formattedValue(c.S, c.IS.String()), nil
+		if xlsx.IS != nil {
+			return f.formattedValue(xlsx.S, xlsx.IS.String()), nil
 		}
-		return f.formattedValue(c.S, c.V), nil
+		return f.formattedValue(xlsx.S, xlsx.V), nil
 	default:
-		isNum, precision := isNumeric(c.V)
-		if isNum && precision > 15 {
-			val, _ := roundPrecision(c.V)
-			if val != c.V {
-				return f.formattedValue(c.S, val), nil
-			}
-		}
-		return f.formattedValue(c.S, c.V), nil
-	}
-}
-
-// roundPrecision round precision for numeric.
-func roundPrecision(value string) (result string, err error) {
-	var num float64
-	if num, err = strconv.ParseFloat(value, 64); err != nil {
-		return
+		return f.formattedValue(xlsx.S, xlsx.V), nil
 	}
-	result = fmt.Sprintf("%g", math.Round(num*numericPrecision)/numericPrecision)
-	return
 }
 
 // SetRowVisible provides a function to set visible of a single row by given
@@ -403,12 +331,12 @@ func (f *File) SetRowVisible(sheet string, row int, visible bool) error {
 		return newInvalidRowNumberError(row)
 	}
 
-	ws, err := f.workSheetReader(sheet)
+	xlsx, err := f.workSheetReader(sheet)
 	if err != nil {
 		return err
 	}
-	prepareSheetXML(ws, 0, row)
-	ws.SheetData.Row[row-1].Hidden = !visible
+	prepareSheetXML(xlsx, 0, row)
+	xlsx.SheetData.Row[row-1].Hidden = !visible
 	return nil
 }
 
@@ -423,14 +351,14 @@ func (f *File) GetRowVisible(sheet string, row int) (bool, error) {
 		return false, newInvalidRowNumberError(row)
 	}
 
-	ws, err := f.workSheetReader(sheet)
+	xlsx, err := f.workSheetReader(sheet)
 	if err != nil {
 		return false, err
 	}
-	if row > len(ws.SheetData.Row) {
+	if row > len(xlsx.SheetData.Row) {
 		return false, nil
 	}
-	return !ws.SheetData.Row[row-1].Hidden, nil
+	return !xlsx.SheetData.Row[row-1].Hidden, nil
 }
 
 // SetRowOutlineLevel provides a function to set outline level number of a
@@ -444,14 +372,14 @@ func (f *File) SetRowOutlineLevel(sheet string, row int, level uint8) error {
 		return newInvalidRowNumberError(row)
 	}
 	if level > 7 || level < 1 {
-		return ErrOutlineLevel
+		return errors.New("invalid outline level")
 	}
-	ws, err := f.workSheetReader(sheet)
+	xlsx, err := f.workSheetReader(sheet)
 	if err != nil {
 		return err
 	}
-	prepareSheetXML(ws, 0, row)
-	ws.SheetData.Row[row-1].OutlineLevel = level
+	prepareSheetXML(xlsx, 0, row)
+	xlsx.SheetData.Row[row-1].OutlineLevel = level
 	return nil
 }
 
@@ -465,14 +393,14 @@ func (f *File) GetRowOutlineLevel(sheet string, row int) (uint8, error) {
 	if row < 1 {
 		return 0, newInvalidRowNumberError(row)
 	}
-	ws, err := f.workSheetReader(sheet)
+	xlsx, err := f.workSheetReader(sheet)
 	if err != nil {
 		return 0, err
 	}
-	if row > len(ws.SheetData.Row) {
+	if row > len(xlsx.SheetData.Row) {
 		return 0, nil
 	}
-	return ws.SheetData.Row[row-1].OutlineLevel, nil
+	return xlsx.SheetData.Row[row-1].OutlineLevel, nil
 }
 
 // RemoveRow provides a function to remove single row by given worksheet name
@@ -489,22 +417,22 @@ func (f *File) RemoveRow(sheet string, row int) error {
 		return newInvalidRowNumberError(row)
 	}
 
-	ws, err := f.workSheetReader(sheet)
+	xlsx, err := f.workSheetReader(sheet)
 	if err != nil {
 		return err
 	}
-	if row > len(ws.SheetData.Row) {
+	if row > len(xlsx.SheetData.Row) {
 		return f.adjustHelper(sheet, rows, row, -1)
 	}
 	keep := 0
-	for rowIdx := 0; rowIdx < len(ws.SheetData.Row); rowIdx++ {
-		v := &ws.SheetData.Row[rowIdx]
+	for rowIdx := 0; rowIdx < len(xlsx.SheetData.Row); rowIdx++ {
+		v := &xlsx.SheetData.Row[rowIdx]
 		if v.R != row {
-			ws.SheetData.Row[keep] = *v
+			xlsx.SheetData.Row[keep] = *v
 			keep++
 		}
 	}
-	ws.SheetData.Row = ws.SheetData.Row[:keep]
+	xlsx.SheetData.Row = xlsx.SheetData.Row[:keep]
 	return f.adjustHelper(sheet, rows, row, -1)
 }
 
@@ -551,20 +479,20 @@ func (f *File) DuplicateRowTo(sheet string, row, row2 int) error {
 		return newInvalidRowNumberError(row)
 	}
 
-	ws, err := f.workSheetReader(sheet)
+	xlsx, err := f.workSheetReader(sheet)
 	if err != nil {
 		return err
 	}
-	if row > len(ws.SheetData.Row) || row2 < 1 || row == row2 {
+	if row > len(xlsx.SheetData.Row) || row2 < 1 || row == row2 {
 		return nil
 	}
 
 	var ok bool
 	var rowCopy xlsxRow
 
-	for i, r := range ws.SheetData.Row {
+	for i, r := range xlsx.SheetData.Row {
 		if r.R == row {
-			rowCopy = deepcopy.Copy(ws.SheetData.Row[i]).(xlsxRow)
+			rowCopy = xlsx.SheetData.Row[i]
 			ok = true
 			break
 		}
@@ -578,13 +506,13 @@ func (f *File) DuplicateRowTo(sheet string, row, row2 int) error {
 	}
 
 	idx2 := -1
-	for i, r := range ws.SheetData.Row {
+	for i, r := range xlsx.SheetData.Row {
 		if r.R == row2 {
 			idx2 = i
 			break
 		}
 	}
-	if idx2 == -1 && len(ws.SheetData.Row) >= row2 {
+	if idx2 == -1 && len(xlsx.SheetData.Row) >= row2 {
 		return nil
 	}
 
@@ -592,23 +520,23 @@ func (f *File) DuplicateRowTo(sheet string, row, row2 int) error {
 	f.ajustSingleRowDimensions(&rowCopy, row2)
 
 	if idx2 != -1 {
-		ws.SheetData.Row[idx2] = rowCopy
+		xlsx.SheetData.Row[idx2] = rowCopy
 	} else {
-		ws.SheetData.Row = append(ws.SheetData.Row, rowCopy)
+		xlsx.SheetData.Row = append(xlsx.SheetData.Row, rowCopy)
 	}
-	return f.duplicateMergeCells(sheet, ws, row, row2)
+	return f.duplicateMergeCells(sheet, xlsx, row, row2)
 }
 
 // duplicateMergeCells merge cells in the destination row if there are single
 // row merged cells in the copied row.
-func (f *File) duplicateMergeCells(sheet string, ws *xlsxWorksheet, row, row2 int) error {
-	if ws.MergeCells == nil {
+func (f *File) duplicateMergeCells(sheet string, xlsx *xlsxWorksheet, row, row2 int) error {
+	if xlsx.MergeCells == nil {
 		return nil
 	}
 	if row > row2 {
 		row++
 	}
-	for _, rng := range ws.MergeCells.Cells {
+	for _, rng := range xlsx.MergeCells.Cells {
 		coordinates, err := f.areaRefToCoordinates(rng.Ref)
 		if err != nil {
 			return err
@@ -617,8 +545,8 @@ func (f *File) duplicateMergeCells(sheet string, ws *xlsxWorksheet, row, row2 in
 			return nil
 		}
 	}
-	for i := 0; i < len(ws.MergeCells.Cells); i++ {
-		areaData := ws.MergeCells.Cells[i]
+	for i := 0; i < len(xlsx.MergeCells.Cells); i++ {
+		areaData := xlsx.MergeCells.Cells[i]
 		coordinates, _ := f.areaRefToCoordinates(areaData.Ref)
 		x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3]
 		if y1 == y2 && y1 == row {
@@ -627,6 +555,7 @@ func (f *File) duplicateMergeCells(sheet string, ws *xlsxWorksheet, row, row2 in
 			if err := f.MergeCell(sheet, from, to); err != nil {
 				return err
 			}
+			i++
 		}
 	}
 	return nil
@@ -656,30 +585,14 @@ func (f *File) duplicateMergeCells(sheet string, ws *xlsxWorksheet, row, row2 in
 //
 // Noteice: this method could be very slow for large spreadsheets (more than
 // 3000 rows one sheet).
-func checkRow(ws *xlsxWorksheet) error {
-	for rowIdx := range ws.SheetData.Row {
-		rowData := &ws.SheetData.Row[rowIdx]
+func checkRow(xlsx *xlsxWorksheet) error {
+	for rowIdx := range xlsx.SheetData.Row {
+		rowData := &xlsx.SheetData.Row[rowIdx]
 
 		colCount := len(rowData.C)
 		if colCount == 0 {
 			continue
 		}
-		// check and fill the cell without r attribute in a row element
-		rCount := 0
-		for idx, cell := range rowData.C {
-			rCount++
-			if cell.R != "" {
-				lastR, _, err := CellNameToCoordinates(cell.R)
-				if err != nil {
-					return err
-				}
-				if lastR > rCount {
-					rCount = lastR
-				}
-				continue
-			}
-			rowData.C[idx].R, _ = CoordinatesToCellName(rCount, rowIdx+1)
-		}
 		lastCol, _, err := CellNameToCoordinates(rowData.C[colCount-1].R)
 		if err != nil {
 			return err
@@ -689,7 +602,7 @@ func checkRow(ws *xlsxWorksheet) error {
 			oldList := rowData.C
 			newlist := make([]xlsxC, 0, lastCol)
 
-			rowData.C = ws.SheetData.Row[rowIdx].C[:0]
+			rowData.C = xlsx.SheetData.Row[rowIdx].C[:0]
 
 			for colIdx := 0; colIdx < lastCol; colIdx++ {
 				cellName, err := CoordinatesToCellName(colIdx+1, rowIdx+1)
@@ -707,7 +620,7 @@ func checkRow(ws *xlsxWorksheet) error {
 				if err != nil {
 					return err
 				}
-				ws.SheetData.Row[rowIdx].C[colNum-1] = *colData
+				xlsx.SheetData.Row[rowIdx].C[colNum-1] = *colData
 			}
 		}
 	}

+ 108 - 175
rows_test.go

@@ -46,10 +46,6 @@ func TestRows(t *testing.T) {
 	f.XLSX["xl/worksheets/sheet1.xml"] = []byte(`<worksheet><sheetData><row r="1"><c r="A1" t="s"><v>1</v></c></row><row r="A"><c r="2" t="str"><v>B</v></c></row></sheetData></worksheet>`)
 	_, err = f.Rows("Sheet1")
 	assert.EqualError(t, err, `strconv.Atoi: parsing "A": invalid syntax`)
-
-	f.XLSX["xl/worksheets/sheet1.xml"] = nil
-	_, err = f.Rows("Sheet1")
-	assert.NoError(t, err)
 }
 
 func TestRowsIterator(t *testing.T) {
@@ -86,59 +82,49 @@ func TestRowsIterator(t *testing.T) {
 }
 
 func TestRowsError(t *testing.T) {
-	f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
+	xlsx, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
 	if !assert.NoError(t, err) {
 		t.FailNow()
 	}
-	_, err = f.Rows("SheetN")
+	_, err = xlsx.Rows("SheetN")
 	assert.EqualError(t, err, "sheet SheetN is not exist")
 }
 
 func TestRowHeight(t *testing.T) {
-	f := NewFile()
-	sheet1 := f.GetSheetName(0)
+	xlsx := NewFile()
+	sheet1 := xlsx.GetSheetName(1)
 
-	assert.EqualError(t, f.SetRowHeight(sheet1, 0, defaultRowHeightPixels+1.0), "invalid row number 0")
+	assert.EqualError(t, xlsx.SetRowHeight(sheet1, 0, defaultRowHeightPixels+1.0), "invalid row number 0")
 
-	_, err := f.GetRowHeight("Sheet1", 0)
+	_, err := xlsx.GetRowHeight("Sheet1", 0)
 	assert.EqualError(t, err, "invalid row number 0")
 
-	assert.NoError(t, f.SetRowHeight(sheet1, 1, 111.0))
-	height, err := f.GetRowHeight(sheet1, 1)
+	assert.NoError(t, xlsx.SetRowHeight(sheet1, 1, 111.0))
+	height, err := xlsx.GetRowHeight(sheet1, 1)
 	assert.NoError(t, err)
 	assert.Equal(t, 111.0, height)
 
-	// Test set row height overflow max row height limit.
-	assert.EqualError(t, f.SetRowHeight(sheet1, 4, MaxRowHeight+1), ErrMaxRowHeight.Error())
+	assert.NoError(t, xlsx.SetRowHeight(sheet1, 4, 444.0))
+	height, err = xlsx.GetRowHeight(sheet1, 4)
+	assert.NoError(t, err)
+	assert.Equal(t, 444.0, height)
 
 	// Test get row height that rows index over exists rows.
-	height, err = f.GetRowHeight(sheet1, 5)
+	height, err = xlsx.GetRowHeight(sheet1, 5)
 	assert.NoError(t, err)
-	assert.Equal(t, defaultRowHeight, height)
+	assert.Equal(t, defaultRowHeightPixels, height)
 
 	// Test get row height that rows heights haven't changed.
-	height, err = f.GetRowHeight(sheet1, 3)
+	height, err = xlsx.GetRowHeight(sheet1, 3)
 	assert.NoError(t, err)
-	assert.Equal(t, defaultRowHeight, height)
+	assert.Equal(t, defaultRowHeightPixels, height)
 
 	// Test set and get row height on not exists worksheet.
-	assert.EqualError(t, f.SetRowHeight("SheetN", 1, 111.0), "sheet SheetN is not exist")
-	_, err = f.GetRowHeight("SheetN", 3)
+	assert.EqualError(t, xlsx.SetRowHeight("SheetN", 1, 111.0), "sheet SheetN is not exist")
+	_, err = xlsx.GetRowHeight("SheetN", 3)
 	assert.EqualError(t, err, "sheet SheetN is not exist")
 
-	// Test get row height with custom default row height.
-	assert.NoError(t, f.SetSheetFormatPr(sheet1,
-		DefaultRowHeight(30.0),
-		CustomHeight(true),
-	))
-	height, err = f.GetRowHeight(sheet1, 100)
-	assert.NoError(t, err)
-	assert.Equal(t, 30.0, height)
-
-	// Test set row height with custom default row height with prepare XML.
-	assert.NoError(t, f.SetCellValue(sheet1, "A10", "A10"))
-
-	err = f.SaveAs(filepath.Join("test", "TestRowHeight.xlsx"))
+	err = xlsx.SaveAs(filepath.Join("test", "TestRowHeight.xlsx"))
 	if !assert.NoError(t, err) {
 		t.FailNow()
 	}
@@ -183,8 +169,6 @@ func TestSharedStringsReader(t *testing.T) {
 	f := NewFile()
 	f.XLSX["xl/sharedStrings.xml"] = MacintoshCyrillicCharset
 	f.sharedStringsReader()
-	si := xlsxSI{}
-	assert.EqualValues(t, "", si.String())
 }
 
 func TestRowVisibility(t *testing.T) {
@@ -215,7 +199,7 @@ func TestRowVisibility(t *testing.T) {
 
 func TestRemoveRow(t *testing.T) {
 	f := NewFile()
-	sheet1 := f.GetSheetName(0)
+	sheet1 := f.GetSheetName(1)
 	r, err := f.workSheetReader(sheet1)
 	assert.NoError(t, err)
 	const (
@@ -275,49 +259,49 @@ func TestRemoveRow(t *testing.T) {
 }
 
 func TestInsertRow(t *testing.T) {
-	f := NewFile()
-	sheet1 := f.GetSheetName(0)
-	r, err := f.workSheetReader(sheet1)
+	xlsx := NewFile()
+	sheet1 := xlsx.GetSheetName(1)
+	r, err := xlsx.workSheetReader(sheet1)
 	assert.NoError(t, err)
 	const (
 		colCount = 10
 		rowCount = 10
 	)
-	fillCells(f, sheet1, colCount, rowCount)
+	fillCells(xlsx, sheet1, colCount, rowCount)
 
-	assert.NoError(t, f.SetCellHyperLink(sheet1, "A5", "https://github.com/360EntSecGroup-Skylar/excelize", "External"))
+	assert.NoError(t, xlsx.SetCellHyperLink(sheet1, "A5", "https://github.com/360EntSecGroup-Skylar/excelize", "External"))
 
-	assert.EqualError(t, f.InsertRow(sheet1, -1), "invalid row number -1")
+	assert.EqualError(t, xlsx.InsertRow(sheet1, -1), "invalid row number -1")
 
-	assert.EqualError(t, f.InsertRow(sheet1, 0), "invalid row number 0")
+	assert.EqualError(t, xlsx.InsertRow(sheet1, 0), "invalid row number 0")
 
-	assert.NoError(t, f.InsertRow(sheet1, 1))
+	assert.NoError(t, xlsx.InsertRow(sheet1, 1))
 	if !assert.Len(t, r.SheetData.Row, rowCount+1) {
 		t.FailNow()
 	}
 
-	assert.NoError(t, f.InsertRow(sheet1, 4))
+	assert.NoError(t, xlsx.InsertRow(sheet1, 4))
 	if !assert.Len(t, r.SheetData.Row, rowCount+2) {
 		t.FailNow()
 	}
 
-	assert.NoError(t, f.SaveAs(filepath.Join("test", "TestInsertRow.xlsx")))
+	assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestInsertRow.xlsx")))
 }
 
 // Testing internal sructure state after insert operations.
 // It is important for insert workflow to be constant to avoid side effect with functions related to internal structure.
 func TestInsertRowInEmptyFile(t *testing.T) {
-	f := NewFile()
-	sheet1 := f.GetSheetName(0)
-	r, err := f.workSheetReader(sheet1)
+	xlsx := NewFile()
+	sheet1 := xlsx.GetSheetName(1)
+	r, err := xlsx.workSheetReader(sheet1)
 	assert.NoError(t, err)
-	assert.NoError(t, f.InsertRow(sheet1, 1))
+	assert.NoError(t, xlsx.InsertRow(sheet1, 1))
 	assert.Len(t, r.SheetData.Row, 0)
-	assert.NoError(t, f.InsertRow(sheet1, 2))
+	assert.NoError(t, xlsx.InsertRow(sheet1, 2))
 	assert.Len(t, r.SheetData.Row, 0)
-	assert.NoError(t, f.InsertRow(sheet1, 99))
+	assert.NoError(t, xlsx.InsertRow(sheet1, 99))
 	assert.Len(t, r.SheetData.Row, 0)
-	assert.NoError(t, f.SaveAs(filepath.Join("test", "TestInsertRowInEmptyFile.xlsx")))
+	assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestInsertRowInEmptyFile.xlsx")))
 }
 
 func TestDuplicateRowFromSingleRow(t *testing.T) {
@@ -334,12 +318,12 @@ func TestDuplicateRowFromSingleRow(t *testing.T) {
 	}
 
 	t.Run("FromSingleRow", func(t *testing.T) {
-		f := NewFile()
-		assert.NoError(t, f.SetCellStr(sheet, "A1", cells["A1"]))
-		assert.NoError(t, f.SetCellStr(sheet, "B1", cells["B1"]))
+		xlsx := NewFile()
+		assert.NoError(t, xlsx.SetCellStr(sheet, "A1", cells["A1"]))
+		assert.NoError(t, xlsx.SetCellStr(sheet, "B1", cells["B1"]))
 
-		assert.NoError(t, f.DuplicateRow(sheet, 1))
-		if !assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, "FromSingleRow_1"))) {
+		assert.NoError(t, xlsx.DuplicateRow(sheet, 1))
+		if !assert.NoError(t, xlsx.SaveAs(fmt.Sprintf(outFile, "TestDuplicateRow.FromSingleRow_1"))) {
 			t.FailNow()
 		}
 		expect := map[string]string{
@@ -347,15 +331,15 @@ func TestDuplicateRowFromSingleRow(t *testing.T) {
 			"A2": cells["A1"], "B2": cells["B1"],
 		}
 		for cell, val := range expect {
-			v, err := f.GetCellValue(sheet, cell)
+			v, err := xlsx.GetCellValue(sheet, cell)
 			assert.NoError(t, err)
 			if !assert.Equal(t, val, v, cell) {
 				t.FailNow()
 			}
 		}
 
-		assert.NoError(t, f.DuplicateRow(sheet, 2))
-		if !assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, "FromSingleRow_2"))) {
+		assert.NoError(t, xlsx.DuplicateRow(sheet, 2))
+		if !assert.NoError(t, xlsx.SaveAs(fmt.Sprintf(outFile, "TestDuplicateRow.FromSingleRow_2"))) {
 			t.FailNow()
 		}
 		expect = map[string]string{
@@ -364,7 +348,7 @@ func TestDuplicateRowFromSingleRow(t *testing.T) {
 			"A3": cells["A1"], "B3": cells["B1"],
 		}
 		for cell, val := range expect {
-			v, err := f.GetCellValue(sheet, cell)
+			v, err := xlsx.GetCellValue(sheet, cell)
 			assert.NoError(t, err)
 			if !assert.Equal(t, val, v, cell) {
 				t.FailNow()
@@ -387,16 +371,16 @@ func TestDuplicateRowUpdateDuplicatedRows(t *testing.T) {
 	}
 
 	t.Run("UpdateDuplicatedRows", func(t *testing.T) {
-		f := NewFile()
-		assert.NoError(t, f.SetCellStr(sheet, "A1", cells["A1"]))
-		assert.NoError(t, f.SetCellStr(sheet, "B1", cells["B1"]))
+		xlsx := NewFile()
+		assert.NoError(t, xlsx.SetCellStr(sheet, "A1", cells["A1"]))
+		assert.NoError(t, xlsx.SetCellStr(sheet, "B1", cells["B1"]))
 
-		assert.NoError(t, f.DuplicateRow(sheet, 1))
+		assert.NoError(t, xlsx.DuplicateRow(sheet, 1))
 
-		assert.NoError(t, f.SetCellStr(sheet, "A2", cells["A2"]))
-		assert.NoError(t, f.SetCellStr(sheet, "B2", cells["B2"]))
+		assert.NoError(t, xlsx.SetCellStr(sheet, "A2", cells["A2"]))
+		assert.NoError(t, xlsx.SetCellStr(sheet, "B2", cells["B2"]))
 
-		if !assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, "UpdateDuplicatedRows"))) {
+		if !assert.NoError(t, xlsx.SaveAs(fmt.Sprintf(outFile, "TestDuplicateRow.UpdateDuplicatedRows"))) {
 			t.FailNow()
 		}
 		expect := map[string]string{
@@ -404,7 +388,7 @@ func TestDuplicateRowUpdateDuplicatedRows(t *testing.T) {
 			"A2": cells["A2"], "B2": cells["B2"],
 		}
 		for cell, val := range expect {
-			v, err := f.GetCellValue(sheet, cell)
+			v, err := xlsx.GetCellValue(sheet, cell)
 			assert.NoError(t, err)
 			if !assert.Equal(t, val, v, cell) {
 				t.FailNow()
@@ -435,11 +419,11 @@ func TestDuplicateRowFirstOfMultipleRows(t *testing.T) {
 	}
 
 	t.Run("FirstOfMultipleRows", func(t *testing.T) {
-		f := newFileWithDefaults()
+		xlsx := newFileWithDefaults()
 
-		assert.NoError(t, f.DuplicateRow(sheet, 1))
+		assert.NoError(t, xlsx.DuplicateRow(sheet, 1))
 
-		if !assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, "FirstOfMultipleRows"))) {
+		if !assert.NoError(t, xlsx.SaveAs(fmt.Sprintf(outFile, "TestDuplicateRow.FirstOfMultipleRows"))) {
 			t.FailNow()
 		}
 		expect := map[string]string{
@@ -449,7 +433,7 @@ func TestDuplicateRowFirstOfMultipleRows(t *testing.T) {
 			"A4": cells["A3"], "B4": cells["B3"],
 		}
 		for cell, val := range expect {
-			v, err := f.GetCellValue(sheet, cell)
+			v, err := xlsx.GetCellValue(sheet, cell)
 			assert.NoError(t, err)
 			if !assert.Equal(t, val, v, cell) {
 				t.FailNow()
@@ -463,24 +447,24 @@ func TestDuplicateRowZeroWithNoRows(t *testing.T) {
 	outFile := filepath.Join("test", "TestDuplicateRow.%s.xlsx")
 
 	t.Run("ZeroWithNoRows", func(t *testing.T) {
-		f := NewFile()
+		xlsx := NewFile()
 
-		assert.EqualError(t, f.DuplicateRow(sheet, 0), "invalid row number 0")
+		assert.EqualError(t, xlsx.DuplicateRow(sheet, 0), "invalid row number 0")
 
-		if !assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, "ZeroWithNoRows"))) {
+		if !assert.NoError(t, xlsx.SaveAs(fmt.Sprintf(outFile, "TestDuplicateRow.ZeroWithNoRows"))) {
 			t.FailNow()
 		}
 
-		val, err := f.GetCellValue(sheet, "A1")
+		val, err := xlsx.GetCellValue(sheet, "A1")
 		assert.NoError(t, err)
 		assert.Equal(t, "", val)
-		val, err = f.GetCellValue(sheet, "B1")
+		val, err = xlsx.GetCellValue(sheet, "B1")
 		assert.NoError(t, err)
 		assert.Equal(t, "", val)
-		val, err = f.GetCellValue(sheet, "A2")
+		val, err = xlsx.GetCellValue(sheet, "A2")
 		assert.NoError(t, err)
 		assert.Equal(t, "", val)
-		val, err = f.GetCellValue(sheet, "B2")
+		val, err = xlsx.GetCellValue(sheet, "B2")
 		assert.NoError(t, err)
 		assert.Equal(t, "", val)
 
@@ -491,7 +475,7 @@ func TestDuplicateRowZeroWithNoRows(t *testing.T) {
 		}
 
 		for cell, val := range expect {
-			v, err := f.GetCellValue(sheet, cell)
+			v, err := xlsx.GetCellValue(sheet, cell)
 			assert.NoError(t, err)
 			if !assert.Equal(t, val, v, cell) {
 				t.FailNow()
@@ -505,11 +489,11 @@ func TestDuplicateRowMiddleRowOfEmptyFile(t *testing.T) {
 	outFile := filepath.Join("test", "TestDuplicateRow.%s.xlsx")
 
 	t.Run("MiddleRowOfEmptyFile", func(t *testing.T) {
-		f := NewFile()
+		xlsx := NewFile()
 
-		assert.NoError(t, f.DuplicateRow(sheet, 99))
+		assert.NoError(t, xlsx.DuplicateRow(sheet, 99))
 
-		if !assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, "MiddleRowOfEmptyFile"))) {
+		if !assert.NoError(t, xlsx.SaveAs(fmt.Sprintf(outFile, "TestDuplicateRow.MiddleRowOfEmptyFile"))) {
 			t.FailNow()
 		}
 		expect := map[string]string{
@@ -518,7 +502,7 @@ func TestDuplicateRowMiddleRowOfEmptyFile(t *testing.T) {
 			"A100": "",
 		}
 		for cell, val := range expect {
-			v, err := f.GetCellValue(sheet, cell)
+			v, err := xlsx.GetCellValue(sheet, cell)
 			assert.NoError(t, err)
 			if !assert.Equal(t, val, v, cell) {
 				t.FailNow()
@@ -549,11 +533,11 @@ func TestDuplicateRowWithLargeOffsetToMiddleOfData(t *testing.T) {
 	}
 
 	t.Run("WithLargeOffsetToMiddleOfData", func(t *testing.T) {
-		f := newFileWithDefaults()
+		xlsx := newFileWithDefaults()
 
-		assert.NoError(t, f.DuplicateRowTo(sheet, 1, 3))
+		assert.NoError(t, xlsx.DuplicateRowTo(sheet, 1, 3))
 
-		if !assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, "WithLargeOffsetToMiddleOfData"))) {
+		if !assert.NoError(t, xlsx.SaveAs(fmt.Sprintf(outFile, "TestDuplicateRow.WithLargeOffsetToMiddleOfData"))) {
 			t.FailNow()
 		}
 		expect := map[string]string{
@@ -563,7 +547,7 @@ func TestDuplicateRowWithLargeOffsetToMiddleOfData(t *testing.T) {
 			"A4": cells["A3"], "B4": cells["B3"],
 		}
 		for cell, val := range expect {
-			v, err := f.GetCellValue(sheet, cell)
+			v, err := xlsx.GetCellValue(sheet, cell)
 			assert.NoError(t, err)
 			if !assert.Equal(t, val, v, cell) {
 				t.FailNow()
@@ -594,11 +578,11 @@ func TestDuplicateRowWithLargeOffsetToEmptyRows(t *testing.T) {
 	}
 
 	t.Run("WithLargeOffsetToEmptyRows", func(t *testing.T) {
-		f := newFileWithDefaults()
+		xlsx := newFileWithDefaults()
 
-		assert.NoError(t, f.DuplicateRowTo(sheet, 1, 7))
+		assert.NoError(t, xlsx.DuplicateRowTo(sheet, 1, 7))
 
-		if !assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, "WithLargeOffsetToEmptyRows"))) {
+		if !assert.NoError(t, xlsx.SaveAs(fmt.Sprintf(outFile, "TestDuplicateRow.WithLargeOffsetToEmptyRows"))) {
 			t.FailNow()
 		}
 		expect := map[string]string{
@@ -608,7 +592,7 @@ func TestDuplicateRowWithLargeOffsetToEmptyRows(t *testing.T) {
 			"A7": cells["A1"], "B7": cells["B1"],
 		}
 		for cell, val := range expect {
-			v, err := f.GetCellValue(sheet, cell)
+			v, err := xlsx.GetCellValue(sheet, cell)
 			assert.NoError(t, err)
 			if !assert.Equal(t, val, v, cell) {
 				t.FailNow()
@@ -639,11 +623,11 @@ func TestDuplicateRowInsertBefore(t *testing.T) {
 	}
 
 	t.Run("InsertBefore", func(t *testing.T) {
-		f := newFileWithDefaults()
+		xlsx := newFileWithDefaults()
 
-		assert.NoError(t, f.DuplicateRowTo(sheet, 2, 1))
+		assert.NoError(t, xlsx.DuplicateRowTo(sheet, 2, 1))
 
-		if !assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, "InsertBefore"))) {
+		if !assert.NoError(t, xlsx.SaveAs(fmt.Sprintf(outFile, "TestDuplicateRow.InsertBefore"))) {
 			t.FailNow()
 		}
 
@@ -654,7 +638,7 @@ func TestDuplicateRowInsertBefore(t *testing.T) {
 			"A4": cells["A3"], "B4": cells["B3"],
 		}
 		for cell, val := range expect {
-			v, err := f.GetCellValue(sheet, cell)
+			v, err := xlsx.GetCellValue(sheet, cell)
 			assert.NoError(t, err)
 			if !assert.Equal(t, val, v, cell) {
 				t.FailNow()
@@ -685,11 +669,11 @@ func TestDuplicateRowInsertBeforeWithLargeOffset(t *testing.T) {
 	}
 
 	t.Run("InsertBeforeWithLargeOffset", func(t *testing.T) {
-		f := newFileWithDefaults()
+		xlsx := newFileWithDefaults()
 
-		assert.NoError(t, f.DuplicateRowTo(sheet, 3, 1))
+		assert.NoError(t, xlsx.DuplicateRowTo(sheet, 3, 1))
 
-		if !assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, "InsertBeforeWithLargeOffset"))) {
+		if !assert.NoError(t, xlsx.SaveAs(fmt.Sprintf(outFile, "TestDuplicateRow.InsertBeforeWithLargeOffset"))) {
 			t.FailNow()
 		}
 
@@ -700,7 +684,7 @@ func TestDuplicateRowInsertBeforeWithLargeOffset(t *testing.T) {
 			"A4": cells["A3"], "B4": cells["B3"],
 		}
 		for cell, val := range expect {
-			v, err := f.GetCellValue(sheet, cell)
+			v, err := xlsx.GetCellValue(sheet, cell)
 			assert.NoError(t, err)
 			if !assert.Equal(t, val, v) {
 				t.FailNow()
@@ -733,12 +717,12 @@ func TestDuplicateRowInsertBeforeWithMergeCells(t *testing.T) {
 	}
 
 	t.Run("InsertBeforeWithLargeOffset", func(t *testing.T) {
-		f := newFileWithDefaults()
+		xlsx := newFileWithDefaults()
 
-		assert.NoError(t, f.DuplicateRowTo(sheet, 2, 1))
-		assert.NoError(t, f.DuplicateRowTo(sheet, 1, 8))
+		assert.NoError(t, xlsx.DuplicateRowTo(sheet, 2, 1))
+		assert.NoError(t, xlsx.DuplicateRowTo(sheet, 1, 8))
 
-		if !assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, "InsertBeforeWithMergeCells"))) {
+		if !assert.NoError(t, xlsx.SaveAs(fmt.Sprintf(outFile, "TestDuplicateRow.InsertBeforeWithMergeCells"))) {
 			t.FailNow()
 		}
 
@@ -748,7 +732,7 @@ func TestDuplicateRowInsertBeforeWithMergeCells(t *testing.T) {
 			{"B1:C1", "B2 Value"},
 		}
 
-		mergeCells, err := f.GetMergeCells(sheet)
+		mergeCells, err := xlsx.GetMergeCells(sheet)
 		assert.NoError(t, err)
 		for idx, val := range expect {
 			if !assert.Equal(t, val, mergeCells[idx]) {
@@ -758,9 +742,9 @@ func TestDuplicateRowInsertBeforeWithMergeCells(t *testing.T) {
 	})
 }
 
-func TestDuplicateRowInvalidRowNum(t *testing.T) {
+func TestDuplicateRowInvalidRownum(t *testing.T) {
 	const sheet = "Sheet1"
-	outFile := filepath.Join("test", "TestDuplicateRow.InvalidRowNum.%s.xlsx")
+	outFile := filepath.Join("test", "TestDuplicateRowInvalidRownum.%s.xlsx")
 
 	cells := map[string]string{
 		"A1": "A1 Value",
@@ -776,21 +760,21 @@ func TestDuplicateRowInvalidRowNum(t *testing.T) {
 	for _, row := range invalidIndexes {
 		name := fmt.Sprintf("%d", row)
 		t.Run(name, func(t *testing.T) {
-			f := NewFile()
+			xlsx := NewFile()
 			for col, val := range cells {
-				assert.NoError(t, f.SetCellStr(sheet, col, val))
+				assert.NoError(t, xlsx.SetCellStr(sheet, col, val))
 			}
 
-			assert.EqualError(t, f.DuplicateRow(sheet, row), fmt.Sprintf("invalid row number %d", row))
+			assert.EqualError(t, xlsx.DuplicateRow(sheet, row), fmt.Sprintf("invalid row number %d", row))
 
 			for col, val := range cells {
-				v, err := f.GetCellValue(sheet, col)
+				v, err := xlsx.GetCellValue(sheet, col)
 				assert.NoError(t, err)
 				if !assert.Equal(t, val, v) {
 					t.FailNow()
 				}
 			}
-			assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, name)))
+			assert.NoError(t, xlsx.SaveAs(fmt.Sprintf(outFile, name)))
 		})
 	}
 
@@ -798,21 +782,21 @@ func TestDuplicateRowInvalidRowNum(t *testing.T) {
 		for _, row2 := range invalidIndexes {
 			name := fmt.Sprintf("[%d,%d]", row1, row2)
 			t.Run(name, func(t *testing.T) {
-				f := NewFile()
+				xlsx := NewFile()
 				for col, val := range cells {
-					assert.NoError(t, f.SetCellStr(sheet, col, val))
+					assert.NoError(t, xlsx.SetCellStr(sheet, col, val))
 				}
 
-				assert.EqualError(t, f.DuplicateRowTo(sheet, row1, row2), fmt.Sprintf("invalid row number %d", row1))
+				assert.EqualError(t, xlsx.DuplicateRowTo(sheet, row1, row2), fmt.Sprintf("invalid row number %d", row1))
 
 				for col, val := range cells {
-					v, err := f.GetCellValue(sheet, col)
+					v, err := xlsx.GetCellValue(sheet, col)
 					assert.NoError(t, err)
 					if !assert.Equal(t, val, v) {
 						t.FailNow()
 					}
 				}
-				assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, name)))
+				assert.NoError(t, xlsx.SaveAs(fmt.Sprintf(outFile, name)))
 			})
 		}
 	}
@@ -825,15 +809,15 @@ func TestDuplicateRowTo(t *testing.T) {
 
 func TestDuplicateMergeCells(t *testing.T) {
 	f := File{}
-	ws := &xlsxWorksheet{MergeCells: &xlsxMergeCells{
+	xlsx := &xlsxWorksheet{MergeCells: &xlsxMergeCells{
 		Cells: []*xlsxMergeCell{{Ref: "A1:-"}},
 	}}
-	assert.EqualError(t, f.duplicateMergeCells("Sheet1", ws, 0, 0), `cannot convert cell "-" to coordinates: invalid cell name "-"`)
-	ws.MergeCells.Cells[0].Ref = "A1:B1"
-	assert.EqualError(t, f.duplicateMergeCells("SheetN", ws, 1, 2), "sheet SheetN is not exist")
+	assert.EqualError(t, f.duplicateMergeCells("Sheet1", xlsx, 0, 0), `cannot convert cell "-" to coordinates: invalid cell name "-"`)
+	xlsx.MergeCells.Cells[0].Ref = "A1:B1"
+	assert.EqualError(t, f.duplicateMergeCells("SheetN", xlsx, 1, 2), "sheet SheetN is not exist")
 }
 
-func TestGetValueFromInlineStr(t *testing.T) {
+func TestGetValueFrom(t *testing.T) {
 	c := &xlsxC{T: "inlineStr"}
 	f := NewFile()
 	d := &xlsxSST{}
@@ -842,62 +826,11 @@ func TestGetValueFromInlineStr(t *testing.T) {
 	assert.Equal(t, "", val)
 }
 
-func TestGetValueFromNumber(t *testing.T) {
-	c := &xlsxC{T: "n", V: "2.2200000000000002"}
-	f := NewFile()
-	d := &xlsxSST{}
-	val, err := c.getValueFrom(f, d)
-	assert.NoError(t, err)
-	assert.Equal(t, "2.22", val)
-
-	c = &xlsxC{T: "n", V: "2.220000ddsf0000000002-r"}
-	val, err = c.getValueFrom(f, d)
-	assert.NoError(t, err)
-	assert.Equal(t, "2.220000ddsf0000000002-r", val)
-
-	c = &xlsxC{T: "n", V: "2.2."}
-	val, err = c.getValueFrom(f, d)
-	assert.NoError(t, err)
-	assert.Equal(t, "2.2.", val)
-}
-
 func TestErrSheetNotExistError(t *testing.T) {
 	err := ErrSheetNotExist{SheetName: "Sheet1"}
 	assert.EqualValues(t, err.Error(), "sheet Sheet1 is not exist")
 }
 
-func TestCheckRow(t *testing.T) {
-	f := NewFile()
-	f.XLSX["xl/worksheets/sheet1.xml"] = []byte(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?><worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" ><sheetData><row r="2"><c><v>1</v></c><c r="F2"><v>2</v></c><c><v>3</v></c><c><v>4</v></c><c r="M2"><v>5</v></c></row></sheetData></worksheet>`)
-	_, err := f.GetRows("Sheet1")
-	assert.NoError(t, err)
-	assert.NoError(t, f.SetCellValue("Sheet1", "A1", false))
-	f = NewFile()
-	f.XLSX["xl/worksheets/sheet1.xml"] = []byte(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?><worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" ><sheetData><row r="2"><c><v>1</v></c><c r="-"><v>2</v></c><c><v>3</v></c><c><v>4</v></c><c r="M2"><v>5</v></c></row></sheetData></worksheet>`)
-	assert.EqualError(t, f.SetCellValue("Sheet1", "A1", false), `cannot convert cell "-" to coordinates: invalid cell name "-"`)
-}
-
-func TestNumberFormats(t *testing.T) {
-	f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
-	if !assert.NoError(t, err) {
-		t.FailNow()
-	}
-	cells := make([][]string, 0)
-	cols, err := f.Cols("Sheet2")
-	if !assert.NoError(t, err) {
-		t.FailNow()
-	}
-	for cols.Next() {
-		col, err := cols.Rows()
-		assert.NoError(t, err)
-		if err != nil {
-			break
-		}
-		cells = append(cells, col)
-	}
-	assert.Equal(t, []string{"", "200", "450", "200", "510", "315", "127", "89", "348", "53", "37"}, cells[3])
-}
-
 func BenchmarkRows(b *testing.B) {
 	f, _ := OpenFile(filepath.Join("test", "Book1.xlsx"))
 	for i := 0; i < b.N; i++ {

+ 8 - 11
shape.go

@@ -1,13 +1,11 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
+// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
 // this source code is governed by a BSD-style license that can be found in
 // the LICENSE file.
 //
 // Package excelize providing a set of functions that allow you to write to
-// and read from XLSX / XLSM / XLTM files. Supports reading and writing
-// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
-// complex components by high compatibility, and provided streaming API for
-// generating or reading data from a worksheet with huge amounts of data. This
-// library needs Go version 1.15 or later.
+// and read from XLSX files. Support reads and writes XLSX file generated by
+// Microsoft Excel™ 2007 and later. Support save file without losing original
+// charts of XLSX. This library needs Go version 1.10 or later.
 
 package excelize
 
@@ -261,7 +259,7 @@ func (f *File) AddShape(sheet, cell, format string) error {
 		return err
 	}
 	// Read sheet data.
-	ws, err := f.workSheetReader(sheet)
+	xlsx, err := f.workSheetReader(sheet)
 	if err != nil {
 		return err
 	}
@@ -270,9 +268,9 @@ func (f *File) AddShape(sheet, cell, format string) error {
 	drawingXML := "xl/drawings/drawing" + strconv.Itoa(drawingID) + ".xml"
 	sheetRelationshipsDrawingXML := "../drawings/drawing" + strconv.Itoa(drawingID) + ".xml"
 
-	if ws.Drawing != nil {
+	if xlsx.Drawing != nil {
 		// The worksheet already has a shape or chart relationships, use the relationships drawing ../drawings/drawing%d.xml.
-		sheetRelationshipsDrawingXML = f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID)
+		sheetRelationshipsDrawingXML = f.getSheetRelationshipsTargetByID(sheet, xlsx.Drawing.RID)
 		drawingID, _ = strconv.Atoi(strings.TrimSuffix(strings.TrimPrefix(sheetRelationshipsDrawingXML, "../drawings/drawing"), ".xml"))
 		drawingXML = strings.Replace(sheetRelationshipsDrawingXML, "..", "xl", -1)
 	} else {
@@ -280,7 +278,6 @@ func (f *File) AddShape(sheet, cell, format string) error {
 		sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(f.sheetMap[trimSheetName(sheet)], "xl/worksheets/") + ".rels"
 		rID := f.addRels(sheetRels, SourceRelationshipDrawingML, sheetRelationshipsDrawingXML, "")
 		f.addSheetDrawing(sheet, rID)
-		f.addSheetNameSpace(sheet, SourceRelationship)
 	}
 	err = f.addDrawingShape(sheet, drawingXML, cell, formatSet)
 	if err != nil {
@@ -324,7 +321,7 @@ func (f *File) addDrawingShape(sheet, drawingXML, cell string, formatSet *format
 	width := int(float64(formatSet.Width) * formatSet.Format.XScale)
 	height := int(float64(formatSet.Height) * formatSet.Format.YScale)
 
-	colStart, rowStart, colEnd, rowEnd, x2, y2 :=
+	colStart, rowStart, _, _, colEnd, rowEnd, x2, y2 :=
 		f.positionObjectPixels(sheet, colIdx, rowIdx, formatSet.Format.OffsetX, formatSet.Format.OffsetY,
 			width, height)
 	content, cNvPrID := f.drawingParser(drawingXML)

+ 157 - 347
sheet.go

@@ -1,13 +1,11 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
+// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
 // this source code is governed by a BSD-style license that can be found in
 // the LICENSE file.
 //
 // Package excelize providing a set of functions that allow you to write to
-// and read from XLSX / XLSM / XLTM files. Supports reading and writing
-// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
-// complex components by high compatibility, and provided streaming API for
-// generating or reading data from a worksheet with huge amounts of data. This
-// library needs Go version 1.15 or later.
+// and read from XLSX files. Support reads and writes XLSX file generated by
+// Microsoft Excel™ 2007 and later. Support save file without losing original
+// charts of XLSX. This library needs Go version 1.10 or later.
 
 package excelize
 
@@ -22,7 +20,6 @@ import (
 	"log"
 	"os"
 	"path"
-	"path/filepath"
 	"reflect"
 	"regexp"
 	"strconv"
@@ -32,15 +29,13 @@ import (
 	"github.com/mohae/deepcopy"
 )
 
-// NewSheet provides the function to create a new sheet by given a worksheet
-// name and returns the index of the sheets in the workbook
-// (spreadsheet) after it appended. Note that when creating a new spreadsheet
-// file, the default worksheet named `Sheet1` will be created.
+// NewSheet provides function to create a new sheet by given worksheet name.
+// When creating a new XLSX file, the default sheet will be created. Returns
+// the number of sheets in the workbook (file) after appending the new sheet.
 func (f *File) NewSheet(name string) int {
 	// Check if the worksheet already exists
-	index := f.GetSheetIndex(name)
-	if index != -1 {
-		return index
+	if f.GetSheetIndex(name) != 0 {
+		return f.SheetCount
 	}
 	f.DeleteSheet(name)
 	f.SheetCount++
@@ -58,11 +53,11 @@ func (f *File) NewSheet(name string) int {
 	f.setContentTypes("/xl/worksheets/sheet"+strconv.Itoa(sheetID)+".xml", ContentTypeSpreadSheetMLWorksheet)
 	// Create new sheet /xl/worksheets/sheet%d.xml
 	f.setSheet(sheetID, name)
-	// Update workbook.xml.rels
-	rID := f.addRels(f.getWorkbookRelsPath(), SourceRelationshipWorkSheet, fmt.Sprintf("/xl/worksheets/sheet%d.xml", sheetID), "")
-	// Update workbook.xml
+	// Update xl/_rels/workbook.xml.rels
+	rID := f.addRels("xl/_rels/workbook.xml.rels", SourceRelationshipWorkSheet, fmt.Sprintf("worksheets/sheet%d.xml", sheetID), "")
+	// Update xl/workbook.xml
 	f.setWorkbook(name, sheetID, rID)
-	return f.GetSheetIndex(name)
+	return sheetID
 }
 
 // contentTypesReader provides a function to get the pointer to the
@@ -90,85 +85,46 @@ func (f *File) contentTypesWriter() {
 	}
 }
 
-// getWorkbookPath provides a function to get the path of the workbook.xml in
-// the spreadsheet.
-func (f *File) getWorkbookPath() (path string) {
-	if rels := f.relsReader("_rels/.rels"); rels != nil {
-		for _, rel := range rels.Relationships {
-			if rel.Type == SourceRelationshipOfficeDocument {
-				path = strings.TrimPrefix(rel.Target, "/")
-				return
-			}
-		}
-	}
-	return
-}
-
-// getWorkbookRelsPath provides a function to get the path of the workbook.xml.rels
-// in the spreadsheet.
-func (f *File) getWorkbookRelsPath() (path string) {
-	wbPath := f.getWorkbookPath()
-	wbDir := filepath.Dir(wbPath)
-	if wbDir == "." {
-		path = "_rels/" + filepath.Base(wbPath) + ".rels"
-		return
-	}
-	path = strings.TrimPrefix(filepath.Dir(wbPath)+"/_rels/"+filepath.Base(wbPath)+".rels", "/")
-	return
-}
-
-// workbookReader provides a function to get the pointer to the workbook.xml
+// workbookReader provides a function to get the pointer to the xl/workbook.xml
 // structure after deserialization.
 func (f *File) workbookReader() *xlsxWorkbook {
 	var err error
+
 	if f.WorkBook == nil {
-		wbPath := f.getWorkbookPath()
 		f.WorkBook = new(xlsxWorkbook)
-		if _, ok := f.xmlAttr[wbPath]; !ok {
-			d := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(wbPath))))
-			f.xmlAttr[wbPath] = append(f.xmlAttr[wbPath], getRootElement(d)...)
-			f.addNameSpaces(wbPath, SourceRelationship)
-		}
-		if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(wbPath)))).
+		if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML("xl/workbook.xml")))).
 			Decode(f.WorkBook); err != nil && err != io.EOF {
 			log.Printf("xml decode error: %s", err)
 		}
 	}
+
 	return f.WorkBook
 }
 
-// workBookWriter provides a function to save workbook.xml after serialize
+// workBookWriter provides a function to save xl/workbook.xml after serialize
 // structure.
 func (f *File) workBookWriter() {
 	if f.WorkBook != nil {
 		output, _ := xml.Marshal(f.WorkBook)
-		f.saveFileList(f.getWorkbookPath(), replaceRelationshipsBytes(f.replaceNameSpaceBytes(f.getWorkbookPath(), output)))
+		f.saveFileList("xl/workbook.xml", replaceRelationshipsBytes(replaceRelationshipsNameSpaceBytes(output)))
 	}
 }
 
 // workSheetWriter provides a function to save xl/worksheets/sheet%d.xml after
 // serialize structure.
 func (f *File) workSheetWriter() {
-	var arr []byte
-	buffer := bytes.NewBuffer(arr)
-	encoder := xml.NewEncoder(buffer)
 	for p, sheet := range f.Sheet {
 		if sheet != nil {
 			for k, v := range sheet.SheetData.Row {
 				f.Sheet[p].SheetData.Row[k].C = trimCell(v.C)
 			}
-			if sheet.SheetPr != nil || sheet.Drawing != nil || sheet.Hyperlinks != nil || sheet.Picture != nil || sheet.TableParts != nil {
-				f.addNameSpaces(p, SourceRelationship)
-			}
-			// reusing buffer
-			_ = encoder.Encode(sheet)
-			f.saveFileList(p, replaceRelationshipsBytes(f.replaceNameSpaceBytes(p, buffer.Bytes())))
+			output, _ := xml.Marshal(sheet)
+			f.saveFileList(p, replaceRelationshipsBytes(replaceRelationshipsNameSpaceBytes(output)))
 			ok := f.checked[p]
 			if ok {
 				delete(f.Sheet, p)
 				f.checked[p] = false
 			}
-			buffer.Reset()
 		}
 	}
 }
@@ -194,7 +150,7 @@ func trimCell(column []xlsxC) []xlsxC {
 }
 
 // setContentTypes provides a function to read and update property of contents
-// type of the spreadsheet.
+// type of XLSX.
 func (f *File) setContentTypes(partName, contentType string) {
 	content := f.contentTypesReader()
 	content.Overrides = append(content.Overrides, xlsxOverride{
@@ -205,7 +161,7 @@ func (f *File) setContentTypes(partName, contentType string) {
 
 // setSheet provides a function to update sheet property by given index.
 func (f *File) setSheet(index int, name string) {
-	ws := xlsxWorksheet{
+	xlsx := xlsxWorksheet{
 		Dimension: &xlsxDimension{Ref: "A1"},
 		SheetViews: &xlsxSheetViews{
 			SheetView: []xlsxSheetView{{WorkbookViewID: 0}},
@@ -213,12 +169,11 @@ func (f *File) setSheet(index int, name string) {
 	}
 	path := "xl/worksheets/sheet" + strconv.Itoa(index) + ".xml"
 	f.sheetMap[trimSheetName(name)] = path
-	f.Sheet[path] = &ws
-	f.xmlAttr[path] = []xml.Attr{NameSpaceSpreadSheet}
+	f.Sheet[path] = &xlsx
 }
 
-// setWorkbook update workbook property of the spreadsheet. Maximum 31
-// characters are allowed in sheet title.
+// setWorkbook update workbook property of XLSX. Maximum 31 characters are
+// allowed in sheet title.
 func (f *File) setWorkbook(name string, sheetID, rid int) {
 	content := f.workbookReader()
 	content.Sheets.Sheet = append(content.Sheets.Sheet, xlsxSheet{
@@ -235,7 +190,7 @@ func (f *File) relsWriter() {
 		if rel != nil {
 			output, _ := xml.Marshal(rel)
 			if strings.HasPrefix(path, "xl/worksheets/sheet/rels/sheet") {
-				output = f.replaceNameSpaceBytes(path, output)
+				output = replaceRelationshipsNameSpaceBytes(output)
 			}
 			f.saveFileList(path, replaceRelationshipsBytes(output))
 		}
@@ -247,26 +202,26 @@ func (f *File) setAppXML() {
 	f.saveFileList("docProps/app.xml", []byte(templateDocpropsApp))
 }
 
-// replaceRelationshipsBytes; Some tools that read spreadsheet files have very
-// strict requirements about the structure of the input XML. This function is
-// a horrible hack to fix that after the XML marshalling is completed.
+// replaceRelationshipsBytes; Some tools that read XLSX files have very strict
+// requirements about the structure of the input XML. This function is a
+// horrible hack to fix that after the XML marshalling is completed.
 func replaceRelationshipsBytes(content []byte) []byte {
-	oldXmlns := []byte(`xmlns:relationships="http://schemas.openxmlformats.org/officeDocument/2006/relationships" relationships`)
-	newXmlns := []byte("r")
+	oldXmlns := stringToBytes(`xmlns:relationships="http://schemas.openxmlformats.org/officeDocument/2006/relationships" relationships`)
+	newXmlns := stringToBytes("r")
 	return bytesReplace(content, oldXmlns, newXmlns, -1)
 }
 
-// SetActiveSheet provides a function to set the default active sheet of the
-// workbook by a given index. Note that the active index is different from the
-// ID returned by function GetSheetMap(). It should be greater or equal to 0
-// and less than the total worksheet numbers.
+// SetActiveSheet provides function to set default active worksheet of XLSX by
+// given index. Note that active index is different from the index returned by
+// function GetSheetMap(). It should be greater than 0 and less than total
+// worksheet numbers.
 func (f *File) SetActiveSheet(index int) {
-	if index < 0 {
-		index = 0
+	if index < 1 {
+		index = 1
 	}
 	wb := f.workbookReader()
-	for activeTab := range wb.Sheets.Sheet {
-		if activeTab == index {
+	for activeTab, sheet := range wb.Sheets.Sheet {
+		if sheet.SheetID == index {
 			if wb.BookViews == nil {
 				wb.BookViews = &xlsxBookViews{}
 			}
@@ -279,25 +234,25 @@ func (f *File) SetActiveSheet(index int) {
 			}
 		}
 	}
-	for idx, name := range f.GetSheetList() {
-		ws, err := f.workSheetReader(name)
+	for idx, name := range f.GetSheetMap() {
+		xlsx, err := f.workSheetReader(name)
 		if err != nil {
-			// Chartsheet or dialogsheet
+			// Chartsheet
 			return
 		}
-		if ws.SheetViews == nil {
-			ws.SheetViews = &xlsxSheetViews{
+		if xlsx.SheetViews == nil {
+			xlsx.SheetViews = &xlsxSheetViews{
 				SheetView: []xlsxSheetView{{WorkbookViewID: 0}},
 			}
 		}
-		if len(ws.SheetViews.SheetView) > 0 {
-			ws.SheetViews.SheetView[0].TabSelected = false
+		if len(xlsx.SheetViews.SheetView) > 0 {
+			xlsx.SheetViews.SheetView[0].TabSelected = false
 		}
 		if index == idx {
-			if len(ws.SheetViews.SheetView) > 0 {
-				ws.SheetViews.SheetView[0].TabSelected = true
+			if len(xlsx.SheetViews.SheetView) > 0 {
+				xlsx.SheetViews.SheetView[0].TabSelected = true
 			} else {
-				ws.SheetViews.SheetView = append(ws.SheetViews.SheetView, xlsxSheetView{
+				xlsx.SheetViews.SheetView = append(xlsx.SheetViews.SheetView, xlsxSheetView{
 					TabSelected: true,
 				})
 			}
@@ -306,23 +261,8 @@ func (f *File) SetActiveSheet(index int) {
 }
 
 // GetActiveSheetIndex provides a function to get active sheet index of the
-// spreadsheet. If not found the active sheet will be return integer 0.
-func (f *File) GetActiveSheetIndex() (index int) {
-	var sheetID = f.getActiveSheetID()
-	wb := f.workbookReader()
-	if wb != nil {
-		for idx, sheet := range wb.Sheets.Sheet {
-			if sheet.SheetID == sheetID {
-				index = idx
-			}
-		}
-	}
-	return
-}
-
-// getActiveSheetID provides a function to get active sheet ID of the
-// spreadsheet. If not found the active sheet will be return integer 0.
-func (f *File) getActiveSheetID() int {
+// XLSX. If not found the active sheet will be return integer 0.
+func (f *File) GetActiveSheetIndex() int {
 	wb := f.workbookReader()
 	if wb != nil {
 		if wb.BookViews != nil && len(wb.BookViews.WorkBookView) > 0 {
@@ -346,9 +286,6 @@ func (f *File) getActiveSheetID() int {
 func (f *File) SetSheetName(oldName, newName string) {
 	oldName = trimSheetName(oldName)
 	newName = trimSheetName(newName)
-	if newName == oldName {
-		return
-	}
 	content := f.workbookReader()
 	for k, v := range content.Sheets.Sheet {
 		if v.Name == oldName {
@@ -359,62 +296,39 @@ func (f *File) SetSheetName(oldName, newName string) {
 	}
 }
 
-// getSheetNameByID provides a function to get worksheet name of the
-// spreadsheet by given worksheet ID. If given sheet ID is invalid, will
-// return an empty string.
-func (f *File) getSheetNameByID(ID int) string {
+// GetSheetName provides a function to get worksheet name of XLSX by given
+// worksheet index. If given sheet index is invalid, will return an empty
+// string.
+func (f *File) GetSheetName(index int) string {
 	wb := f.workbookReader()
-	if wb == nil || ID < 1 {
+	if wb == nil || index < 1 {
 		return ""
 	}
 	for _, sheet := range wb.Sheets.Sheet {
-		if ID == sheet.SheetID {
+		if index == sheet.SheetID {
 			return sheet.Name
 		}
 	}
 	return ""
 }
 
-// GetSheetName provides a function to get the sheet name of the workbook by
-// the given sheet index. If the given sheet index is invalid, it will return
-// an empty string.
-func (f *File) GetSheetName(index int) (name string) {
-	for idx, sheet := range f.GetSheetList() {
-		if idx == index {
-			name = sheet
-		}
-	}
-	return
-}
-
-// getSheetID provides a function to get worksheet ID of the spreadsheet by
-// given sheet name. If given worksheet name is invalid, will return an
-// integer type value -1.
-func (f *File) getSheetID(name string) int {
-	var ID = -1
-	for sheetID, sheet := range f.GetSheetMap() {
-		if sheet == trimSheetName(name) {
-			ID = sheetID
-		}
-	}
-	return ID
-}
-
-// GetSheetIndex provides a function to get a sheet index of the workbook by
-// the given sheet name. If the given sheet name is invalid or sheet doesn't
-// exist, it will return an integer type value -1.
+// GetSheetIndex provides a function to get worksheet index of XLSX by given
+// sheet name. If given worksheet name is invalid, will return an integer type
+// value 0.
 func (f *File) GetSheetIndex(name string) int {
-	var idx = -1
-	for index, sheet := range f.GetSheetList() {
-		if sheet == trimSheetName(name) {
-			idx = index
+	wb := f.workbookReader()
+	if wb != nil {
+		for _, sheet := range wb.Sheets.Sheet {
+			if sheet.Name == trimSheetName(name) {
+				return sheet.SheetID
+			}
 		}
 	}
-	return idx
+	return 0
 }
 
-// GetSheetMap provides a function to get worksheets, chart sheets, dialog
-// sheets ID and name map of the workbook. For example:
+// GetSheetMap provides a function to get worksheet and chartsheet name and
+// index map of XLSX. For example:
 //
 //    f, err := excelize.OpenFile("Book1.xlsx")
 //    if err != nil {
@@ -435,36 +349,20 @@ func (f *File) GetSheetMap() map[int]string {
 	return sheetMap
 }
 
-// GetSheetList provides a function to get worksheets, chart sheets, and
-// dialog sheets name list of the workbook.
-func (f *File) GetSheetList() (list []string) {
-	wb := f.workbookReader()
-	if wb != nil {
-		for _, sheet := range wb.Sheets.Sheet {
-			list = append(list, sheet.Name)
-		}
-	}
-	return
-}
-
-// getSheetMap provides a function to get worksheet name and XML file path map
-// of the spreadsheet.
+// getSheetMap provides a function to get worksheet and chartsheet name and
+// XML file path map of XLSX.
 func (f *File) getSheetMap() map[string]string {
+	content := f.workbookReader()
+	rels := f.relsReader("xl/_rels/workbook.xml.rels")
 	maps := map[string]string{}
-	for _, v := range f.workbookReader().Sheets.Sheet {
-		for _, rel := range f.relsReader(f.getWorkbookRelsPath()).Relationships {
+	for _, v := range content.Sheets.Sheet {
+		for _, rel := range rels.Relationships {
 			if rel.ID == v.ID {
-				// Construct a target XML as xl/worksheets/sheet%d by split
-				// path, compatible with different types of relative paths in
-				// workbook.xml.rels, for example: worksheets/sheet%d.xml
-				// and /xl/worksheets/sheet%d.xml
-				path := filepath.ToSlash(strings.TrimPrefix(
-					strings.Replace(filepath.Clean(fmt.Sprintf("%s/%s", filepath.Dir(f.getWorkbookPath()), rel.Target)), "\\", "/", -1), "/"))
-				if strings.HasPrefix(rel.Target, "/") {
-					path = filepath.ToSlash(strings.TrimPrefix(strings.Replace(filepath.Clean(rel.Target), "\\", "/", -1), "/"))
-				}
-				if _, ok := f.XLSX[path]; ok {
-					maps[v.Name] = path
+				// Construct a target XML as xl/worksheets/sheet%d by split path, compatible with different types of relative paths in workbook.xml.rels, for example: worksheets/sheet%d.xml and /xl/worksheets/sheet%d.xml
+				pathInfo := strings.Split(rel.Target, "/")
+				pathInfoLen := len(pathInfo)
+				if pathInfoLen > 1 {
+					maps[v.Name] = fmt.Sprintf("xl/%s", strings.Join(pathInfo[pathInfoLen-2:], "/"))
 				}
 			}
 		}
@@ -482,14 +380,13 @@ func (f *File) SetSheetBackground(sheet, picture string) error {
 	}
 	ext, ok := supportImageTypes[path.Ext(picture)]
 	if !ok {
-		return ErrImgExt
+		return errors.New("unsupported image extension")
 	}
 	file, _ := ioutil.ReadFile(picture)
 	name := f.addMedia(file, ext)
 	sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(f.sheetMap[trimSheetName(sheet)], "xl/worksheets/") + ".rels"
 	rID := f.addRels(sheetRels, SourceRelationshipImage, strings.Replace(name, "xl", "..", 1), "")
 	f.addSheetPicture(sheet, rID)
-	f.addSheetNameSpace(sheet, SourceRelationship)
 	f.setContentTypePartImageExtensions()
 	return err
 }
@@ -500,29 +397,12 @@ func (f *File) SetSheetBackground(sheet, picture string) error {
 // value of the deleted worksheet, it will cause a file error when you open it.
 // This function will be invalid when only the one worksheet is left.
 func (f *File) DeleteSheet(name string) {
-	if f.SheetCount == 1 || f.GetSheetIndex(name) == -1 {
+	if f.SheetCount == 1 || f.GetSheetIndex(name) == 0 {
 		return
 	}
 	sheetName := trimSheetName(name)
 	wb := f.workbookReader()
-	wbRels := f.relsReader(f.getWorkbookRelsPath())
-	activeSheetName := f.GetSheetName(f.GetActiveSheetIndex())
-	deleteSheetID := f.getSheetID(name)
-	// Delete and adjust defined names
-	if wb.DefinedNames != nil {
-		for idx := 0; idx < len(wb.DefinedNames.DefinedName); idx++ {
-			dn := wb.DefinedNames.DefinedName[idx]
-			if dn.LocalSheetID != nil {
-				sheetID := *dn.LocalSheetID + 1
-				if sheetID == deleteSheetID {
-					wb.DefinedNames.DefinedName = append(wb.DefinedNames.DefinedName[:idx], wb.DefinedNames.DefinedName[idx+1:]...)
-					idx--
-				} else if sheetID > deleteSheetID {
-					wb.DefinedNames.DefinedName[idx].LocalSheetID = intPtr(*dn.LocalSheetID - 1)
-				}
-			}
-		}
-	}
+	wbRels := f.relsReader("xl/_rels/workbook.xml.rels")
 	for idx, sheet := range wb.Sheets.Sheet {
 		if sheet.Name == sheetName {
 			wb.Sheets.Sheet = append(wb.Sheets.Sheet[:idx], wb.Sheets.Sheet[idx+1:]...)
@@ -530,30 +410,40 @@ func (f *File) DeleteSheet(name string) {
 			if wbRels != nil {
 				for _, rel := range wbRels.Relationships {
 					if rel.ID == sheet.ID {
-						sheetXML = rel.Target
-						rels = "xl/worksheets/_rels/" + strings.TrimPrefix(f.sheetMap[sheetName], "xl/worksheets/") + ".rels"
+						sheetXML = fmt.Sprintf("xl/%s", rel.Target)
+						pathInfo := strings.Split(rel.Target, "/")
+						if len(pathInfo) == 2 {
+							rels = fmt.Sprintf("xl/%s/_rels/%s.rels", pathInfo[0], pathInfo[1])
+						}
 					}
 				}
 			}
 			target := f.deleteSheetFromWorkbookRels(sheet.ID)
 			f.deleteSheetFromContentTypes(target)
-			f.deleteCalcChain(sheet.SheetID, "")
+			f.deleteCalcChain(sheet.SheetID, "") // Delete CalcChain
 			delete(f.sheetMap, sheetName)
 			delete(f.XLSX, sheetXML)
 			delete(f.XLSX, rels)
 			delete(f.Relationships, rels)
 			delete(f.Sheet, sheetXML)
-			delete(f.xmlAttr, sheetXML)
 			f.SheetCount--
 		}
 	}
-	f.SetActiveSheet(f.GetSheetIndex(activeSheetName))
+	if wb.BookViews != nil {
+		for idx, bookView := range wb.BookViews.WorkBookView {
+			if bookView.ActiveTab >= f.SheetCount {
+				wb.BookViews.WorkBookView[idx].ActiveTab--
+			}
+		}
+	}
+	f.SetActiveSheet(len(f.GetSheetMap()))
 }
 
 // deleteSheetFromWorkbookRels provides a function to remove worksheet
-// relationships by given relationships ID in the file workbook.xml.rels.
+// relationships by given relationships ID in the file
+// xl/_rels/workbook.xml.rels.
 func (f *File) deleteSheetFromWorkbookRels(rID string) string {
-	content := f.relsReader(f.getWorkbookRelsPath())
+	content := f.relsReader("xl/_rels/workbook.xml.rels")
 	for k, v := range content.Relationships {
 		if v.ID == rID {
 			content.Relationships = append(content.Relationships[:k], content.Relationships[k+1:]...)
@@ -566,12 +456,9 @@ func (f *File) deleteSheetFromWorkbookRels(rID string) string {
 // deleteSheetFromContentTypes provides a function to remove worksheet
 // relationships by given target name in the file [Content_Types].xml.
 func (f *File) deleteSheetFromContentTypes(target string) {
-	if !strings.HasPrefix(target, "/") {
-		target = "/xl/" + target
-	}
 	content := f.contentTypesReader()
 	for k, v := range content.Overrides {
-		if v.PartName == target {
+		if v.PartName == "/xl/"+target {
 			content.Overrides = append(content.Overrides[:k], content.Overrides[k+1:]...)
 		}
 	}
@@ -587,7 +474,7 @@ func (f *File) deleteSheetFromContentTypes(target string) {
 //    return err
 //
 func (f *File) CopySheet(from, to int) error {
-	if from < 0 || to < 0 || from == to || f.GetSheetName(from) == "" || f.GetSheetName(to) == "" {
+	if from < 1 || to < 1 || from == to || f.GetSheetName(from) == "" || f.GetSheetName(to) == "" {
 		return errors.New("invalid worksheet index")
 	}
 	return f.copySheet(from, to)
@@ -596,14 +483,12 @@ func (f *File) CopySheet(from, to int) error {
 // copySheet provides a function to duplicate a worksheet by gave source and
 // target worksheet name.
 func (f *File) copySheet(from, to int) error {
-	fromSheet := f.GetSheetName(from)
-	sheet, err := f.workSheetReader(fromSheet)
+	sheet, err := f.workSheetReader(f.GetSheetName(from))
 	if err != nil {
 		return err
 	}
 	worksheet := deepcopy.Copy(sheet).(*xlsxWorksheet)
-	toSheetID := strconv.Itoa(f.getSheetID(f.GetSheetName(to)))
-	path := "xl/worksheets/sheet" + toSheetID + ".xml"
+	path := "xl/worksheets/sheet" + strconv.Itoa(to) + ".xml"
 	if len(worksheet.SheetViews.SheetView) > 0 {
 		worksheet.SheetViews.SheetView[0].TabSelected = false
 	}
@@ -611,15 +496,12 @@ func (f *File) copySheet(from, to int) error {
 	worksheet.TableParts = nil
 	worksheet.PageSetUp = nil
 	f.Sheet[path] = worksheet
-	toRels := "xl/worksheets/_rels/sheet" + toSheetID + ".xml.rels"
-	fromRels := "xl/worksheets/_rels/sheet" + strconv.Itoa(f.getSheetID(fromSheet)) + ".xml.rels"
+	toRels := "xl/worksheets/_rels/sheet" + strconv.Itoa(to) + ".xml.rels"
+	fromRels := "xl/worksheets/_rels/sheet" + strconv.Itoa(from) + ".xml.rels"
 	_, ok := f.XLSX[fromRels]
 	if ok {
 		f.XLSX[toRels] = f.XLSX[fromRels]
 	}
-	fromSheetXMLPath := f.sheetMap[trimSheetName(fromSheet)]
-	fromSheetAttr := f.xmlAttr[fromSheetXMLPath]
-	f.xmlAttr[path] = fromSheetAttr
 	return err
 }
 
@@ -763,7 +645,7 @@ func parseFormatPanesSet(formatSet string) (*formatPanes, error) {
 //
 func (f *File) SetPanes(sheet, panes string) error {
 	fs, _ := parseFormatPanesSet(panes)
-	ws, err := f.workSheetReader(sheet)
+	xlsx, err := f.workSheetReader(sheet)
 	if err != nil {
 		return err
 	}
@@ -776,10 +658,10 @@ func (f *File) SetPanes(sheet, panes string) error {
 	if fs.Freeze {
 		p.State = "frozen"
 	}
-	ws.SheetViews.SheetView[len(ws.SheetViews.SheetView)-1].Pane = p
+	xlsx.SheetViews.SheetView[len(xlsx.SheetViews.SheetView)-1].Pane = p
 	if !(fs.Freeze) && !(fs.Split) {
-		if len(ws.SheetViews.SheetView) > 0 {
-			ws.SheetViews.SheetView[len(ws.SheetViews.SheetView)-1].Pane = nil
+		if len(xlsx.SheetViews.SheetView) > 0 {
+			xlsx.SheetViews.SheetView[len(xlsx.SheetViews.SheetView)-1].Pane = nil
 		}
 	}
 	s := []*xlsxSelection{}
@@ -790,7 +672,7 @@ func (f *File) SetPanes(sheet, panes string) error {
 			SQRef:      p.SQRef,
 		})
 	}
-	ws.SheetViews.SheetView[len(ws.SheetViews.SheetView)-1].Selection = s
+	xlsx.SheetViews.SheetView[len(xlsx.SheetViews.SheetView)-1].Selection = s
 	return err
 }
 
@@ -842,7 +724,7 @@ func (f *File) SearchSheet(sheet, value string, reg ...bool) ([]string, error) {
 	if f.Sheet[name] != nil {
 		// flush data
 		output, _ := xml.Marshal(f.Sheet[name])
-		f.saveFileList(name, f.replaceNameSpaceBytes(name, output))
+		f.saveFileList(name, replaceRelationshipsNameSpaceBytes(output))
 	}
 	return f.searchSheet(name, value, regSearch)
 }
@@ -867,18 +749,18 @@ func (f *File) searchSheet(name, value string, regSearch bool) (result []string,
 			}
 			break
 		}
-		switch xmlElement := token.(type) {
+		switch startElement := token.(type) {
 		case xml.StartElement:
-			inElement = xmlElement.Name.Local
+			inElement = startElement.Name.Local
 			if inElement == "row" {
-				row, err = attrValToInt("r", xmlElement.Attr)
+				row, err = attrValToInt("r", startElement.Attr)
 				if err != nil {
 					return
 				}
 			}
 			if inElement == "c" {
 				colCell := xlsxC{}
-				_ = decoder.DecodeElement(&colCell, &xmlElement)
+				_ = decoder.DecodeElement(&colCell, &startElement)
 				val, _ := colCell.getValueFrom(f, d)
 				if regSearch {
 					regex := regexp.MustCompile(value)
@@ -1037,12 +919,12 @@ func attrValToInt(name string, attrs []xml.Attr) (val int, err error) {
 // - No footer on the first page
 //
 func (f *File) SetHeaderFooter(sheet string, settings *FormatHeaderFooter) error {
-	ws, err := f.workSheetReader(sheet)
+	xlsx, err := f.workSheetReader(sheet)
 	if err != nil {
 		return err
 	}
 	if settings == nil {
-		ws.HeaderFooter = nil
+		xlsx.HeaderFooter = nil
 		return err
 	}
 
@@ -1054,7 +936,7 @@ func (f *File) SetHeaderFooter(sheet string, settings *FormatHeaderFooter) error
 			return fmt.Errorf("field %s must be less than 255 characters", v.Type().Field(i).Name)
 		}
 	}
-	ws.HeaderFooter = &xlsxHeaderFooter{
+	xlsx.HeaderFooter = &xlsxHeaderFooter{
 		AlignWithMargins: settings.AlignWithMargins,
 		DifferentFirst:   settings.DifferentFirst,
 		DifferentOddEven: settings.DifferentOddEven,
@@ -1079,7 +961,7 @@ func (f *File) SetHeaderFooter(sheet string, settings *FormatHeaderFooter) error
 //    })
 //
 func (f *File) ProtectSheet(sheet string, settings *FormatSheetProtection) error {
-	ws, err := f.workSheetReader(sheet)
+	xlsx, err := f.workSheetReader(sheet)
 	if err != nil {
 		return err
 	}
@@ -1090,7 +972,7 @@ func (f *File) ProtectSheet(sheet string, settings *FormatSheetProtection) error
 			SelectLockedCells: true,
 		}
 	}
-	ws.SheetProtection = &xlsxSheetProtection{
+	xlsx.SheetProtection = &xlsxSheetProtection{
 		AutoFilter:          settings.AutoFilter,
 		DeleteColumns:       settings.DeleteColumns,
 		DeleteRows:          settings.DeleteRows,
@@ -1109,18 +991,18 @@ func (f *File) ProtectSheet(sheet string, settings *FormatSheetProtection) error
 		Sort:                settings.Sort,
 	}
 	if settings.Password != "" {
-		ws.SheetProtection.Password = genSheetPasswd(settings.Password)
+		xlsx.SheetProtection.Password = genSheetPasswd(settings.Password)
 	}
 	return err
 }
 
 // UnprotectSheet provides a function to unprotect an Excel worksheet.
 func (f *File) UnprotectSheet(sheet string) error {
-	ws, err := f.workSheetReader(sheet)
+	xlsx, err := f.workSheetReader(sheet)
 	if err != nil {
 		return err
 	}
-	ws.SheetProtection = nil
+	xlsx.SheetProtection = nil
 	return err
 }
 
@@ -1158,24 +1040,15 @@ type PageLayoutOptionPtr interface {
 }
 
 type (
-	// BlackAndWhite specified print black and white.
-	BlackAndWhite bool
-	// FirstPageNumber specified the first printed page number. If no value is
-	// specified, then 'automatic' is assumed.
-	FirstPageNumber uint
 	// PageLayoutOrientation defines the orientation of page layout for a
 	// worksheet.
 	PageLayoutOrientation string
-	// PageLayoutPaperSize defines the paper size of the worksheet.
+	// PageLayoutPaperSize defines the paper size of the worksheet
 	PageLayoutPaperSize int
-	// FitToHeight specified the number of vertical pages to fit on.
+	// FitToHeight specified number of vertical pages to fit on
 	FitToHeight int
-	// FitToWidth specified the number of horizontal pages to fit on.
+	// FitToWidth specified number of horizontal pages to fit on
 	FitToWidth int
-	// PageLayoutScale defines the print scaling. This attribute is restricted
-	// to values ranging from 10 (10%) to 400 (400%). This setting is
-	// overridden when fitToWidth and/or fitToHeight are in use.
-	PageLayoutScale uint
 )
 
 const (
@@ -1185,41 +1058,6 @@ const (
 	OrientationLandscape = "landscape"
 )
 
-// setPageLayout provides a method to set the print black and white for the
-// worksheet.
-func (p BlackAndWhite) setPageLayout(ps *xlsxPageSetUp) {
-	ps.BlackAndWhite = bool(p)
-}
-
-// getPageLayout provides a method to get the print black and white for the
-// worksheet.
-func (p *BlackAndWhite) getPageLayout(ps *xlsxPageSetUp) {
-	if ps == nil {
-		*p = false
-		return
-	}
-	*p = BlackAndWhite(ps.BlackAndWhite)
-}
-
-// setPageLayout provides a method to set the first printed page number for
-// the worksheet.
-func (p FirstPageNumber) setPageLayout(ps *xlsxPageSetUp) {
-	if 0 < int(p) {
-		ps.FirstPageNumber = int(p)
-		ps.UseFirstPageNumber = true
-	}
-}
-
-// getPageLayout provides a method to get the first printed page number for
-// the worksheet.
-func (p *FirstPageNumber) getPageLayout(ps *xlsxPageSetUp) {
-	if ps == nil || ps.FirstPageNumber == 0 || !ps.UseFirstPageNumber {
-		*p = 1
-		return
-	}
-	*p = FirstPageNumber(ps.FirstPageNumber)
-}
-
 // setPageLayout provides a method to set the orientation for the worksheet.
 func (o PageLayoutOrientation) setPageLayout(ps *xlsxPageSetUp) {
 	ps.Orientation = string(o)
@@ -1282,33 +1120,11 @@ func (p *FitToWidth) getPageLayout(ps *xlsxPageSetUp) {
 	*p = FitToWidth(ps.FitToWidth)
 }
 
-// setPageLayout provides a method to set the scale for the worksheet.
-func (p PageLayoutScale) setPageLayout(ps *xlsxPageSetUp) {
-	if 10 <= int(p) && int(p) <= 400 {
-		ps.Scale = int(p)
-	}
-}
-
-// getPageLayout provides a method to get the scale for the worksheet.
-func (p *PageLayoutScale) getPageLayout(ps *xlsxPageSetUp) {
-	if ps == nil || ps.Scale < 10 || ps.Scale > 400 {
-		*p = 100
-		return
-	}
-	*p = PageLayoutScale(ps.Scale)
-}
-
 // SetPageLayout provides a function to sets worksheet page layout.
 //
 // Available options:
-//
-//    BlackAndWhite(bool)
-//    FirstPageNumber(uint)
-//    PageLayoutOrientation(string)
-//    PageLayoutPaperSize(int)
-//    FitToHeight(int)
-//    FitToWidth(int)
-//    PageLayoutScale(uint)
+//   PageLayoutOrientation(string)
+//   PageLayoutPaperSize(int)
 //
 // The following shows the paper size sorted by excelize index number:
 //
@@ -1487,15 +1303,16 @@ func (f *File) SetDefinedName(definedName *DefinedName) error {
 		Data:    definedName.RefersTo,
 	}
 	if definedName.Scope != "" {
-		if sheetIndex := f.GetSheetIndex(definedName.Scope); sheetIndex >= 0 {
-			d.LocalSheetID = &sheetIndex
+		if sheetID := f.GetSheetIndex(definedName.Scope); sheetID != 0 {
+			sheetID--
+			d.LocalSheetID = &sheetID
 		}
 	}
 	if wb.DefinedNames != nil {
 		for _, dn := range wb.DefinedNames.DefinedName {
 			var scope string
 			if dn.LocalSheetID != nil {
-				scope = f.getSheetNameByID(*dn.LocalSheetID + 1)
+				scope = f.GetSheetName(*dn.LocalSheetID + 1)
 			}
 			if scope == definedName.Scope && dn.Name == definedName.Name {
 				return errors.New("the same name already exists on the scope")
@@ -1525,7 +1342,7 @@ func (f *File) DeleteDefinedName(definedName *DefinedName) error {
 		for idx, dn := range wb.DefinedNames.DefinedName {
 			var scope string
 			if dn.LocalSheetID != nil {
-				scope = f.getSheetNameByID(*dn.LocalSheetID + 1)
+				scope = f.GetSheetName(*dn.LocalSheetID + 1)
 			}
 			if scope == definedName.Scope && dn.Name == definedName.Name {
 				wb.DefinedNames.DefinedName = append(wb.DefinedNames.DefinedName[:idx], wb.DefinedNames.DefinedName[idx+1:]...)
@@ -1549,8 +1366,8 @@ func (f *File) GetDefinedName() []DefinedName {
 				RefersTo: dn.Data,
 				Scope:    "Workbook",
 			}
-			if dn.LocalSheetID != nil && *dn.LocalSheetID >= 0 {
-				definedName.Scope = f.getSheetNameByID(*dn.LocalSheetID + 1)
+			if dn.LocalSheetID != nil {
+				definedName.Scope = f.GetSheetName(*dn.LocalSheetID + 1)
 			}
 			definedNames = append(definedNames, definedName)
 		}
@@ -1564,7 +1381,7 @@ func (f *File) GroupSheets(sheets []string) error {
 	// check an active worksheet in group worksheets
 	var inActiveSheet bool
 	activeSheet := f.GetActiveSheetIndex()
-	sheetMap := f.GetSheetList()
+	sheetMap := f.GetSheetMap()
 	for idx, sheetName := range sheetMap {
 		for _, s := range sheets {
 			if s == sheetName && idx == activeSheet {
@@ -1576,19 +1393,19 @@ func (f *File) GroupSheets(sheets []string) error {
 		return errors.New("group worksheet must contain an active worksheet")
 	}
 	// check worksheet exists
-	wss := []*xlsxWorksheet{}
+	ws := []*xlsxWorksheet{}
 	for _, sheet := range sheets {
-		worksheet, err := f.workSheetReader(sheet)
+		xlsx, err := f.workSheetReader(sheet)
 		if err != nil {
 			return err
 		}
-		wss = append(wss, worksheet)
+		ws = append(ws, xlsx)
 	}
-	for _, ws := range wss {
-		sheetViews := ws.SheetViews.SheetView
+	for _, s := range ws {
+		sheetViews := s.SheetViews.SheetView
 		if len(sheetViews) > 0 {
 			for idx := range sheetViews {
-				ws.SheetViews.SheetView[idx].TabSelected = true
+				s.SheetViews.SheetView[idx].TabSelected = true
 			}
 			continue
 		}
@@ -1599,15 +1416,16 @@ func (f *File) GroupSheets(sheets []string) error {
 // UngroupSheets provides a function to ungroup worksheets.
 func (f *File) UngroupSheets() error {
 	activeSheet := f.GetActiveSheetIndex()
-	for index, sheet := range f.GetSheetList() {
-		if activeSheet == index {
+	sheetMap := f.GetSheetMap()
+	for sheetID, sheet := range sheetMap {
+		if activeSheet == sheetID {
 			continue
 		}
-		ws, _ := f.workSheetReader(sheet)
-		sheetViews := ws.SheetViews.SheetView
+		xlsx, _ := f.workSheetReader(sheet)
+		sheetViews := xlsx.SheetViews.SheetView
 		if len(sheetViews) > 0 {
 			for idx := range sheetViews {
-				ws.SheetViews.SheetView[idx].TabSelected = false
+				xlsx.SheetViews.SheetView[idx].TabSelected = false
 			}
 		}
 	}
@@ -1746,29 +1564,22 @@ func (f *File) relsReader(path string) *xlsxRelationships {
 // fillSheetData ensures there are enough rows, and columns in the chosen
 // row to accept data. Missing rows are backfilled and given their row number
 // Uses the last populated row as a hint for the size of the next row to add
-func prepareSheetXML(ws *xlsxWorksheet, col int, row int) {
-	rowCount := len(ws.SheetData.Row)
+func prepareSheetXML(xlsx *xlsxWorksheet, col int, row int) {
+	rowCount := len(xlsx.SheetData.Row)
 	sizeHint := 0
-	var ht float64
-	var customHeight bool
-	if ws.SheetFormatPr != nil && ws.SheetFormatPr.CustomHeight {
-		ht = ws.SheetFormatPr.DefaultRowHeight
-		customHeight = true
-	}
 	if rowCount > 0 {
-		sizeHint = len(ws.SheetData.Row[rowCount-1].C)
+		sizeHint = len(xlsx.SheetData.Row[rowCount-1].C)
 	}
 	if rowCount < row {
 		// append missing rows
 		for rowIdx := rowCount; rowIdx < row; rowIdx++ {
-			ws.SheetData.Row = append(ws.SheetData.Row, xlsxRow{R: rowIdx + 1, CustomHeight: customHeight, Ht: ht, C: make([]xlsxC, 0, sizeHint)})
+			xlsx.SheetData.Row = append(xlsx.SheetData.Row, xlsxRow{R: rowIdx + 1, C: make([]xlsxC, 0, sizeHint)})
 		}
 	}
-	rowData := &ws.SheetData.Row[row-1]
+	rowData := &xlsx.SheetData.Row[row-1]
 	fillColumns(rowData, col, row)
 }
 
-// fillColumns fill cells in the column of the row as contiguous.
 func fillColumns(rowData *xlsxRow, col, row int) {
 	cellCount := len(rowData.C)
 	if cellCount < col {
@@ -1779,10 +1590,9 @@ func fillColumns(rowData *xlsxRow, col, row int) {
 	}
 }
 
-// makeContiguousColumns make columns in specific rows as contiguous.
-func makeContiguousColumns(ws *xlsxWorksheet, fromRow, toRow, colCount int) {
+func makeContiguousColumns(xlsx *xlsxWorksheet, fromRow, toRow, colCount int) {
 	for ; fromRow < toRow; fromRow++ {
-		rowData := &ws.SheetData.Row[fromRow-1]
+		rowData := &xlsx.SheetData.Row[fromRow-1]
 		fillColumns(rowData, colCount, fromRow)
 	}
 }

+ 55 - 164
sheet_test.go

@@ -1,27 +1,31 @@
-package excelize
+package excelize_test
 
 import (
 	"fmt"
 	"path/filepath"
-	"strconv"
 	"strings"
 	"testing"
 
+	"github.com/360EntSecGroup-Skylar/excelize/v2"
+
 	"github.com/mohae/deepcopy"
 	"github.com/stretchr/testify/assert"
 )
 
 func ExampleFile_SetPageLayout() {
-	f := NewFile()
+	f := excelize.NewFile()
+
 	if err := f.SetPageLayout(
 		"Sheet1",
-		BlackAndWhite(true),
-		FirstPageNumber(2),
-		PageLayoutOrientation(OrientationLandscape),
-		PageLayoutPaperSize(10),
-		FitToHeight(2),
-		FitToWidth(2),
-		PageLayoutScale(50),
+		excelize.PageLayoutOrientation(excelize.OrientationLandscape),
+	); err != nil {
+		fmt.Println(err)
+	}
+	if err := f.SetPageLayout(
+		"Sheet1",
+		excelize.PageLayoutPaperSize(10),
+		excelize.FitToHeight(2),
+		excelize.FitToWidth(2),
 	); err != nil {
 		fmt.Println(err)
 	}
@@ -29,22 +33,13 @@ func ExampleFile_SetPageLayout() {
 }
 
 func ExampleFile_GetPageLayout() {
-	f := NewFile()
+	f := excelize.NewFile()
 	var (
-		blackAndWhite   BlackAndWhite
-		firstPageNumber FirstPageNumber
-		orientation     PageLayoutOrientation
-		paperSize       PageLayoutPaperSize
-		fitToHeight     FitToHeight
-		fitToWidth      FitToWidth
-		scale           PageLayoutScale
+		orientation excelize.PageLayoutOrientation
+		paperSize   excelize.PageLayoutPaperSize
+		fitToHeight excelize.FitToHeight
+		fitToWidth  excelize.FitToWidth
 	)
-	if err := f.GetPageLayout("Sheet1", &blackAndWhite); err != nil {
-		fmt.Println(err)
-	}
-	if err := f.GetPageLayout("Sheet1", &firstPageNumber); err != nil {
-		fmt.Println(err)
-	}
 	if err := f.GetPageLayout("Sheet1", &orientation); err != nil {
 		fmt.Println(err)
 	}
@@ -54,44 +49,34 @@ func ExampleFile_GetPageLayout() {
 	if err := f.GetPageLayout("Sheet1", &fitToHeight); err != nil {
 		fmt.Println(err)
 	}
+
 	if err := f.GetPageLayout("Sheet1", &fitToWidth); err != nil {
 		fmt.Println(err)
 	}
-	if err := f.GetPageLayout("Sheet1", &scale); err != nil {
-		fmt.Println(err)
-	}
 	fmt.Println("Defaults:")
-	fmt.Printf("- print black and white: %t\n", blackAndWhite)
-	fmt.Printf("- page number for first printed page: %d\n", firstPageNumber)
 	fmt.Printf("- orientation: %q\n", orientation)
 	fmt.Printf("- paper size: %d\n", paperSize)
 	fmt.Printf("- fit to height: %d\n", fitToHeight)
 	fmt.Printf("- fit to width: %d\n", fitToWidth)
-	fmt.Printf("- scale: %d\n", scale)
 	// Output:
 	// Defaults:
-	// - print black and white: false
-	// - page number for first printed page: 1
 	// - orientation: "portrait"
 	// - paper size: 1
 	// - fit to height: 1
 	// - fit to width: 1
-	// - scale: 100
 }
 
 func TestNewSheet(t *testing.T) {
-	f := NewFile()
+	f := excelize.NewFile()
 	sheetID := f.NewSheet("Sheet2")
 	f.SetActiveSheet(sheetID)
 	// delete original sheet
 	f.DeleteSheet(f.GetSheetName(f.GetSheetIndex("Sheet1")))
 	assert.NoError(t, f.SaveAs(filepath.Join("test", "TestNewSheet.xlsx")))
-	// create new worksheet with already exists name
-	assert.Equal(t, f.GetSheetIndex("Sheet2"), f.NewSheet("Sheet2"))
 }
 
 func TestSetPane(t *testing.T) {
-	f := NewFile()
+	f := excelize.NewFile()
 	assert.NoError(t, f.SetPanes("Sheet1", `{"freeze":false,"split":false}`))
 	f.NewSheet("Panes 2")
 	assert.NoError(t, f.SetPanes("Panes 2", `{"freeze":true,"split":false,"x_split":1,"y_split":0,"top_left_cell":"B1","active_pane":"topRight","panes":[{"sqref":"K16","active_cell":"K16","pane":"topRight"}]}`))
@@ -108,16 +93,13 @@ func TestPageLayoutOption(t *testing.T) {
 	const sheet = "Sheet1"
 
 	testData := []struct {
-		container  PageLayoutOptionPtr
-		nonDefault PageLayoutOption
+		container  excelize.PageLayoutOptionPtr
+		nonDefault excelize.PageLayoutOption
 	}{
-		{new(BlackAndWhite), BlackAndWhite(true)},
-		{new(FirstPageNumber), FirstPageNumber(2)},
-		{new(PageLayoutOrientation), PageLayoutOrientation(OrientationLandscape)},
-		{new(PageLayoutPaperSize), PageLayoutPaperSize(10)},
-		{new(FitToHeight), FitToHeight(2)},
-		{new(FitToWidth), FitToWidth(2)},
-		{new(PageLayoutScale), PageLayoutScale(50)},
+		{new(excelize.PageLayoutOrientation), excelize.PageLayoutOrientation(excelize.OrientationLandscape)},
+		{new(excelize.PageLayoutPaperSize), excelize.PageLayoutPaperSize(10)},
+		{new(excelize.FitToHeight), excelize.FitToHeight(2)},
+		{new(excelize.FitToWidth), excelize.FitToWidth(2)},
 	}
 
 	for i, test := range testData {
@@ -126,11 +108,11 @@ func TestPageLayoutOption(t *testing.T) {
 			opt := test.nonDefault
 			t.Logf("option %T", opt)
 
-			def := deepcopy.Copy(test.container).(PageLayoutOptionPtr)
-			val1 := deepcopy.Copy(def).(PageLayoutOptionPtr)
-			val2 := deepcopy.Copy(def).(PageLayoutOptionPtr)
+			def := deepcopy.Copy(test.container).(excelize.PageLayoutOptionPtr)
+			val1 := deepcopy.Copy(def).(excelize.PageLayoutOptionPtr)
+			val2 := deepcopy.Copy(def).(excelize.PageLayoutOptionPtr)
 
-			f := NewFile()
+			f := excelize.NewFile()
 			// Get the default value
 			assert.NoError(t, f.GetPageLayout(sheet, def), opt)
 			// Get again and check
@@ -168,7 +150,7 @@ func TestPageLayoutOption(t *testing.T) {
 }
 
 func TestSearchSheet(t *testing.T) {
-	f, err := OpenFile(filepath.Join("test", "SharedStrings.xlsx"))
+	f, err := excelize.OpenFile(filepath.Join("test", "SharedStrings.xlsx"))
 	if !assert.NoError(t, err) {
 		t.FailNow()
 	}
@@ -190,36 +172,36 @@ func TestSearchSheet(t *testing.T) {
 	assert.EqualValues(t, expected, result)
 
 	// Test search worksheet data after set cell value
-	f = NewFile()
+	f = excelize.NewFile()
 	assert.NoError(t, f.SetCellValue("Sheet1", "A1", true))
 	_, err = f.SearchSheet("Sheet1", "")
 	assert.NoError(t, err)
 }
 
 func TestSetPageLayout(t *testing.T) {
-	f := NewFile()
+	f := excelize.NewFile()
 	// Test set page layout on not exists worksheet.
 	assert.EqualError(t, f.SetPageLayout("SheetN"), "sheet SheetN is not exist")
 }
 
 func TestGetPageLayout(t *testing.T) {
-	f := NewFile()
+	f := excelize.NewFile()
 	// Test get page layout on not exists worksheet.
 	assert.EqualError(t, f.GetPageLayout("SheetN"), "sheet SheetN is not exist")
 }
 
 func TestSetHeaderFooter(t *testing.T) {
-	f := NewFile()
+	f := excelize.NewFile()
 	assert.NoError(t, f.SetCellStr("Sheet1", "A1", "Test SetHeaderFooter"))
 	// Test set header and footer on not exists worksheet.
 	assert.EqualError(t, f.SetHeaderFooter("SheetN", nil), "sheet SheetN is not exist")
 	// Test set header and footer with illegal setting.
-	assert.EqualError(t, f.SetHeaderFooter("Sheet1", &FormatHeaderFooter{
+	assert.EqualError(t, f.SetHeaderFooter("Sheet1", &excelize.FormatHeaderFooter{
 		OddHeader: strings.Repeat("c", 256),
 	}), "field OddHeader must be less than 255 characters")
 
 	assert.NoError(t, f.SetHeaderFooter("Sheet1", nil))
-	assert.NoError(t, f.SetHeaderFooter("Sheet1", &FormatHeaderFooter{
+	assert.NoError(t, f.SetHeaderFooter("Sheet1", &excelize.FormatHeaderFooter{
 		DifferentFirst:   true,
 		DifferentOddEven: true,
 		OddHeader:        "&R&P",
@@ -232,28 +214,28 @@ func TestSetHeaderFooter(t *testing.T) {
 }
 
 func TestDefinedName(t *testing.T) {
-	f := NewFile()
-	assert.NoError(t, f.SetDefinedName(&DefinedName{
+	f := excelize.NewFile()
+	assert.NoError(t, f.SetDefinedName(&excelize.DefinedName{
 		Name:     "Amount",
 		RefersTo: "Sheet1!$A$2:$D$5",
 		Comment:  "defined name comment",
 		Scope:    "Sheet1",
 	}))
-	assert.NoError(t, f.SetDefinedName(&DefinedName{
+	assert.NoError(t, f.SetDefinedName(&excelize.DefinedName{
 		Name:     "Amount",
 		RefersTo: "Sheet1!$A$2:$D$5",
 		Comment:  "defined name comment",
 	}))
-	assert.EqualError(t, f.SetDefinedName(&DefinedName{
+	assert.EqualError(t, f.SetDefinedName(&excelize.DefinedName{
 		Name:     "Amount",
 		RefersTo: "Sheet1!$A$2:$D$5",
 		Comment:  "defined name comment",
 	}), "the same name already exists on the scope")
-	assert.EqualError(t, f.DeleteDefinedName(&DefinedName{
+	assert.EqualError(t, f.DeleteDefinedName(&excelize.DefinedName{
 		Name: "No Exist Defined Name",
 	}), "no defined name on the scope")
 	assert.Exactly(t, "Sheet1!$A$2:$D$5", f.GetDefinedName()[1].RefersTo)
-	assert.NoError(t, f.DeleteDefinedName(&DefinedName{
+	assert.NoError(t, f.DeleteDefinedName(&excelize.DefinedName{
 		Name: "Amount",
 	}))
 	assert.Exactly(t, "Sheet1!$A$2:$D$5", f.GetDefinedName()[0].RefersTo)
@@ -262,7 +244,7 @@ func TestDefinedName(t *testing.T) {
 }
 
 func TestGroupSheets(t *testing.T) {
-	f := NewFile()
+	f := excelize.NewFile()
 	sheets := []string{"Sheet2", "Sheet3"}
 	for _, sheet := range sheets {
 		f.NewSheet(sheet)
@@ -274,7 +256,7 @@ func TestGroupSheets(t *testing.T) {
 }
 
 func TestUngroupSheets(t *testing.T) {
-	f := NewFile()
+	f := excelize.NewFile()
 	sheets := []string{"Sheet2", "Sheet3", "Sheet4", "Sheet5"}
 	for _, sheet := range sheets {
 		f.NewSheet(sheet)
@@ -283,7 +265,7 @@ func TestUngroupSheets(t *testing.T) {
 }
 
 func TestInsertPageBreak(t *testing.T) {
-	f := NewFile()
+	f := excelize.NewFile()
 	assert.NoError(t, f.InsertPageBreak("Sheet1", "A1"))
 	assert.NoError(t, f.InsertPageBreak("Sheet1", "B2"))
 	assert.NoError(t, f.InsertPageBreak("Sheet1", "C3"))
@@ -294,7 +276,7 @@ func TestInsertPageBreak(t *testing.T) {
 }
 
 func TestRemovePageBreak(t *testing.T) {
-	f := NewFile()
+	f := excelize.NewFile()
 	assert.NoError(t, f.RemovePageBreak("Sheet1", "A2"))
 
 	assert.NoError(t, f.InsertPageBreak("Sheet1", "A2"))
@@ -320,12 +302,11 @@ func TestRemovePageBreak(t *testing.T) {
 }
 
 func TestGetSheetName(t *testing.T) {
-	f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
-	assert.NoError(t, err)
-	assert.Equal(t, "Sheet1", f.GetSheetName(0))
-	assert.Equal(t, "Sheet2", f.GetSheetName(1))
-	assert.Equal(t, "", f.GetSheetName(-1))
-	assert.Equal(t, "", f.GetSheetName(2))
+	f, _ := excelize.OpenFile(filepath.Join("test", "Book1.xlsx"))
+	assert.Equal(t, "Sheet1", f.GetSheetName(1))
+	assert.Equal(t, "Sheet2", f.GetSheetName(2))
+	assert.Equal(t, "", f.GetSheetName(0))
+	assert.Equal(t, "", f.GetSheetName(3))
 }
 
 func TestGetSheetMap(t *testing.T) {
@@ -333,100 +314,10 @@ func TestGetSheetMap(t *testing.T) {
 		1: "Sheet1",
 		2: "Sheet2",
 	}
-	f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
-	assert.NoError(t, err)
+	f, _ := excelize.OpenFile(filepath.Join("test", "Book1.xlsx"))
 	sheetMap := f.GetSheetMap()
 	for idx, name := range sheetMap {
 		assert.Equal(t, expectedMap[idx], name)
 	}
 	assert.Equal(t, len(sheetMap), 2)
 }
-
-func TestSetActiveSheet(t *testing.T) {
-	f := NewFile()
-	f.WorkBook.BookViews = nil
-	f.SetActiveSheet(1)
-	f.WorkBook.BookViews = &xlsxBookViews{WorkBookView: []xlsxWorkBookView{}}
-	f.Sheet["xl/worksheets/sheet1.xml"].SheetViews = &xlsxSheetViews{SheetView: []xlsxSheetView{}}
-	f.SetActiveSheet(1)
-	f.Sheet["xl/worksheets/sheet1.xml"].SheetViews = nil
-	f.SetActiveSheet(1)
-	f = NewFile()
-	f.SetActiveSheet(-1)
-	assert.Equal(t, f.GetActiveSheetIndex(), 0)
-}
-
-func TestSetSheetName(t *testing.T) {
-	f := NewFile()
-	// Test set workksheet with the same name.
-	f.SetSheetName("Sheet1", "Sheet1")
-	assert.Equal(t, "Sheet1", f.GetSheetName(0))
-}
-
-func TestGetWorkbookPath(t *testing.T) {
-	f := NewFile()
-	delete(f.XLSX, "_rels/.rels")
-	assert.Equal(t, "", f.getWorkbookPath())
-}
-
-func TestGetWorkbookRelsPath(t *testing.T) {
-	f := NewFile()
-	delete(f.XLSX, "xl/_rels/.rels")
-	f.XLSX["_rels/.rels"] = []byte(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://purl.oclc.org/ooxml/officeDocument/relationships/officeDocument" Target="/workbook.xml"/></Relationships>`)
-	assert.Equal(t, "_rels/workbook.xml.rels", f.getWorkbookRelsPath())
-}
-
-func TestDeleteSheet(t *testing.T) {
-	f := NewFile()
-	f.SetActiveSheet(f.NewSheet("Sheet2"))
-	f.NewSheet("Sheet3")
-	f.DeleteSheet("Sheet1")
-	assert.Equal(t, "Sheet2", f.GetSheetName(f.GetActiveSheetIndex()))
-	assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDeleteSheet.xlsx")))
-	// Test with auto filter defined names
-	f = NewFile()
-	f.NewSheet("Sheet2")
-	f.NewSheet("Sheet3")
-	assert.NoError(t, f.SetCellValue("Sheet1", "A1", "A"))
-	assert.NoError(t, f.SetCellValue("Sheet2", "A1", "A"))
-	assert.NoError(t, f.SetCellValue("Sheet3", "A1", "A"))
-	assert.NoError(t, f.AutoFilter("Sheet1", "A1", "A1", ""))
-	assert.NoError(t, f.AutoFilter("Sheet2", "A1", "A1", ""))
-	assert.NoError(t, f.AutoFilter("Sheet3", "A1", "A1", ""))
-	f.DeleteSheet("Sheet2")
-	f.DeleteSheet("Sheet1")
-	assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDeleteSheet2.xlsx")))
-}
-
-func BenchmarkNewSheet(b *testing.B) {
-	b.RunParallel(func(pb *testing.PB) {
-		for pb.Next() {
-			newSheetWithSet()
-		}
-	})
-}
-func newSheetWithSet() {
-	file := NewFile()
-	file.NewSheet("sheet1")
-	for i := 0; i < 1000; i++ {
-		_ = file.SetCellInt("sheet1", "A"+strconv.Itoa(i+1), i)
-	}
-	file = nil
-}
-
-func BenchmarkFile_SaveAs(b *testing.B) {
-	b.RunParallel(func(pb *testing.PB) {
-		for pb.Next() {
-			newSheetWithSave()
-		}
-	})
-}
-
-func newSheetWithSave() {
-	file := NewFile()
-	file.NewSheet("sheet1")
-	for i := 0; i < 1000; i++ {
-		_ = file.SetCellInt("sheet1", "A"+strconv.Itoa(i+1), i)
-	}
-	_ = file.Save()
-}

+ 6 - 227
sheetpr.go

@@ -1,18 +1,14 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
+// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
 // this source code is governed by a BSD-style license that can be found in
 // the LICENSE file.
 //
 // Package excelize providing a set of functions that allow you to write to
-// and read from XLSX / XLSM / XLTM files. Supports reading and writing
-// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
-// complex components by high compatibility, and provided streaming API for
-// generating or reading data from a worksheet with huge amounts of data. This
-// library needs Go version 1.15 or later.
+// and read from XLSX files. Support reads and writes XLSX file generated by
+// Microsoft Excel™ 2007 and later. Support save file without losing original
+// charts of XLSX. This library needs Go version 1.10 or later.
 
 package excelize
 
-import "strings"
-
 // SheetPrOption is an option of a view of a worksheet. See SetSheetPrOptions().
 type SheetPrOption interface {
 	setSheetPrOption(view *xlsxSheetPr)
@@ -33,8 +29,6 @@ type (
 	Published bool
 	// FitToPage is a SheetPrOption
 	FitToPage bool
-	// TabColor is a SheetPrOption
-	TabColor string
 	// AutoPageBreaks is a SheetPrOption
 	AutoPageBreaks bool
 	// OutlineSummaryBelow is an outlinePr, within SheetPr option
@@ -129,28 +123,6 @@ func (o *FitToPage) getSheetPrOption(pr *xlsxSheetPr) {
 	*o = FitToPage(pr.PageSetUpPr.FitToPage)
 }
 
-// setSheetPrOption implements the SheetPrOption interface and specifies a
-// stable name of the sheet.
-func (o TabColor) setSheetPrOption(pr *xlsxSheetPr) {
-	if pr.TabColor == nil {
-		if string(o) == "" {
-			return
-		}
-		pr.TabColor = new(xlsxTabColor)
-	}
-	pr.TabColor.RGB = getPaletteColor(string(o))
-}
-
-// getSheetPrOption implements the SheetPrOptionPtr interface and get the
-// stable name of the sheet.
-func (o *TabColor) getSheetPrOption(pr *xlsxSheetPr) {
-	if pr == nil || pr.TabColor == nil {
-		*o = ""
-		return
-	}
-	*o = TabColor(strings.TrimPrefix(pr.TabColor.RGB, "FF"))
-}
-
 // setSheetPrOption implements the SheetPrOption interface.
 func (o AutoPageBreaks) setSheetPrOption(pr *xlsxSheetPr) {
 	if pr.PageSetUpPr == nil {
@@ -341,7 +313,7 @@ type PageMarginsOptionsPtr interface {
 // SetPageMargins provides a function to set worksheet page margins.
 //
 // Available options:
-//   PageMarginBottom(float64)
+//   PageMarginBotom(float64)
 //   PageMarginFooter(float64)
 //   PageMarginHeader(float64)
 //   PageMarginLeft(float64)
@@ -367,7 +339,7 @@ func (f *File) SetPageMargins(sheet string, opts ...PageMarginsOptions) error {
 // GetPageMargins provides a function to get worksheet page margins.
 //
 // Available options:
-//   PageMarginBottom(float64)
+//   PageMarginBotom(float64)
 //   PageMarginFooter(float64)
 //   PageMarginHeader(float64)
 //   PageMarginLeft(float64)
@@ -385,196 +357,3 @@ func (f *File) GetPageMargins(sheet string, opts ...PageMarginsOptionsPtr) error
 	}
 	return err
 }
-
-// SheetFormatPrOptions is an option of the formatting properties of a
-// worksheet. See SetSheetFormatPr().
-type SheetFormatPrOptions interface {
-	setSheetFormatPr(formatPr *xlsxSheetFormatPr)
-}
-
-// SheetFormatPrOptionsPtr is a writable SheetFormatPrOptions. See
-// GetSheetFormatPr().
-type SheetFormatPrOptionsPtr interface {
-	SheetFormatPrOptions
-	getSheetFormatPr(formatPr *xlsxSheetFormatPr)
-}
-
-type (
-	// BaseColWidth specifies the number of characters of the maximum digit width
-	// of the normal style's font. This value does not include margin padding or
-	// extra padding for gridlines. It is only the number of characters.
-	BaseColWidth uint8
-	// DefaultColWidth specifies the default column width measured as the number
-	// of characters of the maximum digit width of the normal style's font.
-	DefaultColWidth float64
-	// DefaultRowHeight specifies the default row height measured in point size.
-	// Optimization so we don't have to write the height on all rows. This can be
-	// written out if most rows have custom height, to achieve the optimization.
-	DefaultRowHeight float64
-	// CustomHeight specifies the custom height.
-	CustomHeight bool
-	// ZeroHeight specifies if rows are hidden.
-	ZeroHeight bool
-	// ThickTop specifies if rows have a thick top border by default.
-	ThickTop bool
-	// ThickBottom specifies if rows have a thick bottom border by default.
-	ThickBottom bool
-)
-
-// setSheetFormatPr provides a method to set the number of characters of the
-// maximum digit width of the normal style's font.
-func (p BaseColWidth) setSheetFormatPr(fp *xlsxSheetFormatPr) {
-	fp.BaseColWidth = uint8(p)
-}
-
-// setSheetFormatPr provides a method to set the number of characters of the
-// maximum digit width of the normal style's font.
-func (p *BaseColWidth) getSheetFormatPr(fp *xlsxSheetFormatPr) {
-	if fp == nil {
-		*p = 0
-		return
-	}
-	*p = BaseColWidth(fp.BaseColWidth)
-}
-
-// setSheetFormatPr provides a method to set the default column width measured
-// as the number of characters of the maximum digit width of the normal
-// style's font.
-func (p DefaultColWidth) setSheetFormatPr(fp *xlsxSheetFormatPr) {
-	fp.DefaultColWidth = float64(p)
-}
-
-// getSheetFormatPr provides a method to get the default column width measured
-// as the number of characters of the maximum digit width of the normal
-// style's font.
-func (p *DefaultColWidth) getSheetFormatPr(fp *xlsxSheetFormatPr) {
-	if fp == nil {
-		*p = 0
-		return
-	}
-	*p = DefaultColWidth(fp.DefaultColWidth)
-}
-
-// setSheetFormatPr provides a method to set the default row height measured
-// in point size.
-func (p DefaultRowHeight) setSheetFormatPr(fp *xlsxSheetFormatPr) {
-	fp.DefaultRowHeight = float64(p)
-}
-
-// getSheetFormatPr provides a method to get the default row height measured
-// in point size.
-func (p *DefaultRowHeight) getSheetFormatPr(fp *xlsxSheetFormatPr) {
-	if fp == nil {
-		*p = 15
-		return
-	}
-	*p = DefaultRowHeight(fp.DefaultRowHeight)
-}
-
-// setSheetFormatPr provides a method to set the custom height.
-func (p CustomHeight) setSheetFormatPr(fp *xlsxSheetFormatPr) {
-	fp.CustomHeight = bool(p)
-}
-
-// getSheetFormatPr provides a method to get the custom height.
-func (p *CustomHeight) getSheetFormatPr(fp *xlsxSheetFormatPr) {
-	if fp == nil {
-		*p = false
-		return
-	}
-	*p = CustomHeight(fp.CustomHeight)
-}
-
-// setSheetFormatPr provides a method to set if rows are hidden.
-func (p ZeroHeight) setSheetFormatPr(fp *xlsxSheetFormatPr) {
-	fp.ZeroHeight = bool(p)
-}
-
-// getSheetFormatPr provides a method to get if rows are hidden.
-func (p *ZeroHeight) getSheetFormatPr(fp *xlsxSheetFormatPr) {
-	if fp == nil {
-		*p = false
-		return
-	}
-	*p = ZeroHeight(fp.ZeroHeight)
-}
-
-// setSheetFormatPr provides a method to set if rows have a thick top border
-// by default.
-func (p ThickTop) setSheetFormatPr(fp *xlsxSheetFormatPr) {
-	fp.ThickTop = bool(p)
-}
-
-// getSheetFormatPr provides a method to get if rows have a thick top border
-// by default.
-func (p *ThickTop) getSheetFormatPr(fp *xlsxSheetFormatPr) {
-	if fp == nil {
-		*p = false
-		return
-	}
-	*p = ThickTop(fp.ThickTop)
-}
-
-// setSheetFormatPr provides a method to set if rows have a thick bottom
-// border by default.
-func (p ThickBottom) setSheetFormatPr(fp *xlsxSheetFormatPr) {
-	fp.ThickBottom = bool(p)
-}
-
-// setSheetFormatPr provides a method to set if rows have a thick bottom
-// border by default.
-func (p *ThickBottom) getSheetFormatPr(fp *xlsxSheetFormatPr) {
-	if fp == nil {
-		*p = false
-		return
-	}
-	*p = ThickBottom(fp.ThickBottom)
-}
-
-// SetSheetFormatPr provides a function to set worksheet formatting properties.
-//
-// Available options:
-//   BaseColWidth(uint8)
-//   DefaultColWidth(float64)
-//   DefaultRowHeight(float64)
-//   CustomHeight(bool)
-//   ZeroHeight(bool)
-//   ThickTop(bool)
-//   ThickBottom(bool)
-func (f *File) SetSheetFormatPr(sheet string, opts ...SheetFormatPrOptions) error {
-	s, err := f.workSheetReader(sheet)
-	if err != nil {
-		return err
-	}
-	fp := s.SheetFormatPr
-	if fp == nil {
-		fp = new(xlsxSheetFormatPr)
-		s.SheetFormatPr = fp
-	}
-	for _, opt := range opts {
-		opt.setSheetFormatPr(fp)
-	}
-	return err
-}
-
-// GetSheetFormatPr provides a function to get worksheet formatting properties.
-//
-// Available options:
-//   BaseColWidth(uint8)
-//   DefaultColWidth(float64)
-//   DefaultRowHeight(float64)
-//   CustomHeight(bool)
-//   ZeroHeight(bool)
-//   ThickTop(bool)
-//   ThickBottom(bool)
-func (f *File) GetSheetFormatPr(sheet string, opts ...SheetFormatPrOptionsPtr) error {
-	s, err := f.workSheetReader(sheet)
-	if err != nil {
-		return err
-	}
-	fp := s.SheetFormatPr
-	for _, opt := range opts {
-		opt.getSheetFormatPr(fp)
-	}
-	return err
-}

+ 87 - 254
sheetpr_test.go

@@ -1,4 +1,4 @@
-package excelize
+package excelize_test
 
 import (
 	"fmt"
@@ -6,40 +6,39 @@ import (
 
 	"github.com/mohae/deepcopy"
 	"github.com/stretchr/testify/assert"
+
+	"github.com/360EntSecGroup-Skylar/excelize/v2"
 )
 
-var _ = []SheetPrOption{
-	CodeName("hello"),
-	EnableFormatConditionsCalculation(false),
-	Published(false),
-	FitToPage(true),
-	TabColor("#FFFF00"),
-	AutoPageBreaks(true),
-	OutlineSummaryBelow(true),
+var _ = []excelize.SheetPrOption{
+	excelize.CodeName("hello"),
+	excelize.EnableFormatConditionsCalculation(false),
+	excelize.Published(false),
+	excelize.FitToPage(true),
+	excelize.AutoPageBreaks(true),
+	excelize.OutlineSummaryBelow(true),
 }
 
-var _ = []SheetPrOptionPtr{
-	(*CodeName)(nil),
-	(*EnableFormatConditionsCalculation)(nil),
-	(*Published)(nil),
-	(*FitToPage)(nil),
-	(*TabColor)(nil),
-	(*AutoPageBreaks)(nil),
-	(*OutlineSummaryBelow)(nil),
+var _ = []excelize.SheetPrOptionPtr{
+	(*excelize.CodeName)(nil),
+	(*excelize.EnableFormatConditionsCalculation)(nil),
+	(*excelize.Published)(nil),
+	(*excelize.FitToPage)(nil),
+	(*excelize.AutoPageBreaks)(nil),
+	(*excelize.OutlineSummaryBelow)(nil),
 }
 
 func ExampleFile_SetSheetPrOptions() {
-	f := NewFile()
+	f := excelize.NewFile()
 	const sheet = "Sheet1"
 
 	if err := f.SetSheetPrOptions(sheet,
-		CodeName("code"),
-		EnableFormatConditionsCalculation(false),
-		Published(false),
-		FitToPage(true),
-		TabColor("#FFFF00"),
-		AutoPageBreaks(true),
-		OutlineSummaryBelow(false),
+		excelize.CodeName("code"),
+		excelize.EnableFormatConditionsCalculation(false),
+		excelize.Published(false),
+		excelize.FitToPage(true),
+		excelize.AutoPageBreaks(true),
+		excelize.OutlineSummaryBelow(false),
 	); err != nil {
 		fmt.Println(err)
 	}
@@ -47,17 +46,16 @@ func ExampleFile_SetSheetPrOptions() {
 }
 
 func ExampleFile_GetSheetPrOptions() {
-	f := NewFile()
+	f := excelize.NewFile()
 	const sheet = "Sheet1"
 
 	var (
-		codeName                          CodeName
-		enableFormatConditionsCalculation EnableFormatConditionsCalculation
-		published                         Published
-		fitToPage                         FitToPage
-		tabColor                          TabColor
-		autoPageBreaks                    AutoPageBreaks
-		outlineSummaryBelow               OutlineSummaryBelow
+		codeName                          excelize.CodeName
+		enableFormatConditionsCalculation excelize.EnableFormatConditionsCalculation
+		published                         excelize.Published
+		fitToPage                         excelize.FitToPage
+		autoPageBreaks                    excelize.AutoPageBreaks
+		outlineSummaryBelow               excelize.OutlineSummaryBelow
 	)
 
 	if err := f.GetSheetPrOptions(sheet,
@@ -65,7 +63,6 @@ func ExampleFile_GetSheetPrOptions() {
 		&enableFormatConditionsCalculation,
 		&published,
 		&fitToPage,
-		&tabColor,
 		&autoPageBreaks,
 		&outlineSummaryBelow,
 	); err != nil {
@@ -76,7 +73,6 @@ func ExampleFile_GetSheetPrOptions() {
 	fmt.Println("- enableFormatConditionsCalculation:", enableFormatConditionsCalculation)
 	fmt.Println("- published:", published)
 	fmt.Println("- fitToPage:", fitToPage)
-	fmt.Printf("- tabColor: %q\n", tabColor)
 	fmt.Println("- autoPageBreaks:", autoPageBreaks)
 	fmt.Println("- outlineSummaryBelow:", outlineSummaryBelow)
 	// Output:
@@ -85,7 +81,6 @@ func ExampleFile_GetSheetPrOptions() {
 	// - enableFormatConditionsCalculation: true
 	// - published: true
 	// - fitToPage: false
-	// - tabColor: ""
 	// - autoPageBreaks: false
 	// - outlineSummaryBelow: true
 }
@@ -94,16 +89,15 @@ func TestSheetPrOptions(t *testing.T) {
 	const sheet = "Sheet1"
 
 	testData := []struct {
-		container  SheetPrOptionPtr
-		nonDefault SheetPrOption
+		container  excelize.SheetPrOptionPtr
+		nonDefault excelize.SheetPrOption
 	}{
-		{new(CodeName), CodeName("xx")},
-		{new(EnableFormatConditionsCalculation), EnableFormatConditionsCalculation(false)},
-		{new(Published), Published(false)},
-		{new(FitToPage), FitToPage(true)},
-		{new(TabColor), TabColor("FFFF00")},
-		{new(AutoPageBreaks), AutoPageBreaks(true)},
-		{new(OutlineSummaryBelow), OutlineSummaryBelow(false)},
+		{new(excelize.CodeName), excelize.CodeName("xx")},
+		{new(excelize.EnableFormatConditionsCalculation), excelize.EnableFormatConditionsCalculation(false)},
+		{new(excelize.Published), excelize.Published(false)},
+		{new(excelize.FitToPage), excelize.FitToPage(true)},
+		{new(excelize.AutoPageBreaks), excelize.AutoPageBreaks(true)},
+		{new(excelize.OutlineSummaryBelow), excelize.OutlineSummaryBelow(false)},
 	}
 
 	for i, test := range testData {
@@ -112,11 +106,11 @@ func TestSheetPrOptions(t *testing.T) {
 			opt := test.nonDefault
 			t.Logf("option %T", opt)
 
-			def := deepcopy.Copy(test.container).(SheetPrOptionPtr)
-			val1 := deepcopy.Copy(def).(SheetPrOptionPtr)
-			val2 := deepcopy.Copy(def).(SheetPrOptionPtr)
+			def := deepcopy.Copy(test.container).(excelize.SheetPrOptionPtr)
+			val1 := deepcopy.Copy(def).(excelize.SheetPrOptionPtr)
+			val2 := deepcopy.Copy(def).(excelize.SheetPrOptionPtr)
 
-			f := NewFile()
+			f := excelize.NewFile()
 			// Get the default value
 			assert.NoError(t, f.GetSheetPrOptions(sheet, def), opt)
 			// Get again and check
@@ -154,47 +148,46 @@ func TestSheetPrOptions(t *testing.T) {
 }
 
 func TestSetSheetrOptions(t *testing.T) {
-	f := NewFile()
-	assert.NoError(t, f.SetSheetPrOptions("Sheet1", TabColor("")))
+	f := excelize.NewFile()
 	// Test SetSheetrOptions on not exists worksheet.
 	assert.EqualError(t, f.SetSheetPrOptions("SheetN"), "sheet SheetN is not exist")
 }
 
 func TestGetSheetPrOptions(t *testing.T) {
-	f := NewFile()
+	f := excelize.NewFile()
 	// Test GetSheetPrOptions on not exists worksheet.
 	assert.EqualError(t, f.GetSheetPrOptions("SheetN"), "sheet SheetN is not exist")
 }
 
-var _ = []PageMarginsOptions{
-	PageMarginBottom(1.0),
-	PageMarginFooter(1.0),
-	PageMarginHeader(1.0),
-	PageMarginLeft(1.0),
-	PageMarginRight(1.0),
-	PageMarginTop(1.0),
+var _ = []excelize.PageMarginsOptions{
+	excelize.PageMarginBottom(1.0),
+	excelize.PageMarginFooter(1.0),
+	excelize.PageMarginHeader(1.0),
+	excelize.PageMarginLeft(1.0),
+	excelize.PageMarginRight(1.0),
+	excelize.PageMarginTop(1.0),
 }
 
-var _ = []PageMarginsOptionsPtr{
-	(*PageMarginBottom)(nil),
-	(*PageMarginFooter)(nil),
-	(*PageMarginHeader)(nil),
-	(*PageMarginLeft)(nil),
-	(*PageMarginRight)(nil),
-	(*PageMarginTop)(nil),
+var _ = []excelize.PageMarginsOptionsPtr{
+	(*excelize.PageMarginBottom)(nil),
+	(*excelize.PageMarginFooter)(nil),
+	(*excelize.PageMarginHeader)(nil),
+	(*excelize.PageMarginLeft)(nil),
+	(*excelize.PageMarginRight)(nil),
+	(*excelize.PageMarginTop)(nil),
 }
 
 func ExampleFile_SetPageMargins() {
-	f := NewFile()
+	f := excelize.NewFile()
 	const sheet = "Sheet1"
 
 	if err := f.SetPageMargins(sheet,
-		PageMarginBottom(1.0),
-		PageMarginFooter(1.0),
-		PageMarginHeader(1.0),
-		PageMarginLeft(1.0),
-		PageMarginRight(1.0),
-		PageMarginTop(1.0),
+		excelize.PageMarginBottom(1.0),
+		excelize.PageMarginFooter(1.0),
+		excelize.PageMarginHeader(1.0),
+		excelize.PageMarginLeft(1.0),
+		excelize.PageMarginRight(1.0),
+		excelize.PageMarginTop(1.0),
 	); err != nil {
 		fmt.Println(err)
 	}
@@ -202,16 +195,16 @@ func ExampleFile_SetPageMargins() {
 }
 
 func ExampleFile_GetPageMargins() {
-	f := NewFile()
+	f := excelize.NewFile()
 	const sheet = "Sheet1"
 
 	var (
-		marginBottom PageMarginBottom
-		marginFooter PageMarginFooter
-		marginHeader PageMarginHeader
-		marginLeft   PageMarginLeft
-		marginRight  PageMarginRight
-		marginTop    PageMarginTop
+		marginBottom excelize.PageMarginBottom
+		marginFooter excelize.PageMarginFooter
+		marginHeader excelize.PageMarginHeader
+		marginLeft   excelize.PageMarginLeft
+		marginRight  excelize.PageMarginRight
+		marginTop    excelize.PageMarginTop
 	)
 
 	if err := f.GetPageMargins(sheet,
@@ -245,15 +238,15 @@ func TestPageMarginsOption(t *testing.T) {
 	const sheet = "Sheet1"
 
 	testData := []struct {
-		container  PageMarginsOptionsPtr
-		nonDefault PageMarginsOptions
+		container  excelize.PageMarginsOptionsPtr
+		nonDefault excelize.PageMarginsOptions
 	}{
-		{new(PageMarginTop), PageMarginTop(1.0)},
-		{new(PageMarginBottom), PageMarginBottom(1.0)},
-		{new(PageMarginLeft), PageMarginLeft(1.0)},
-		{new(PageMarginRight), PageMarginRight(1.0)},
-		{new(PageMarginHeader), PageMarginHeader(1.0)},
-		{new(PageMarginFooter), PageMarginFooter(1.0)},
+		{new(excelize.PageMarginTop), excelize.PageMarginTop(1.0)},
+		{new(excelize.PageMarginBottom), excelize.PageMarginBottom(1.0)},
+		{new(excelize.PageMarginLeft), excelize.PageMarginLeft(1.0)},
+		{new(excelize.PageMarginRight), excelize.PageMarginRight(1.0)},
+		{new(excelize.PageMarginHeader), excelize.PageMarginHeader(1.0)},
+		{new(excelize.PageMarginFooter), excelize.PageMarginFooter(1.0)},
 	}
 
 	for i, test := range testData {
@@ -262,11 +255,11 @@ func TestPageMarginsOption(t *testing.T) {
 			opt := test.nonDefault
 			t.Logf("option %T", opt)
 
-			def := deepcopy.Copy(test.container).(PageMarginsOptionsPtr)
-			val1 := deepcopy.Copy(def).(PageMarginsOptionsPtr)
-			val2 := deepcopy.Copy(def).(PageMarginsOptionsPtr)
+			def := deepcopy.Copy(test.container).(excelize.PageMarginsOptionsPtr)
+			val1 := deepcopy.Copy(def).(excelize.PageMarginsOptionsPtr)
+			val2 := deepcopy.Copy(def).(excelize.PageMarginsOptionsPtr)
 
-			f := NewFile()
+			f := excelize.NewFile()
 			// Get the default value
 			assert.NoError(t, f.GetPageMargins(sheet, def), opt)
 			// Get again and check
@@ -304,173 +297,13 @@ func TestPageMarginsOption(t *testing.T) {
 }
 
 func TestSetPageMargins(t *testing.T) {
-	f := NewFile()
+	f := excelize.NewFile()
 	// Test set page margins on not exists worksheet.
 	assert.EqualError(t, f.SetPageMargins("SheetN"), "sheet SheetN is not exist")
 }
 
 func TestGetPageMargins(t *testing.T) {
-	f := NewFile()
+	f := excelize.NewFile()
 	// Test get page margins on not exists worksheet.
 	assert.EqualError(t, f.GetPageMargins("SheetN"), "sheet SheetN is not exist")
 }
-
-func ExampleFile_SetSheetFormatPr() {
-	f := NewFile()
-	const sheet = "Sheet1"
-
-	if err := f.SetSheetFormatPr(sheet,
-		BaseColWidth(1.0),
-		DefaultColWidth(1.0),
-		DefaultRowHeight(1.0),
-		CustomHeight(true),
-		ZeroHeight(true),
-		ThickTop(true),
-		ThickBottom(true),
-	); err != nil {
-		fmt.Println(err)
-	}
-	// Output:
-}
-
-func ExampleFile_GetSheetFormatPr() {
-	f := NewFile()
-	const sheet = "Sheet1"
-
-	var (
-		baseColWidth     BaseColWidth
-		defaultColWidth  DefaultColWidth
-		defaultRowHeight DefaultRowHeight
-		customHeight     CustomHeight
-		zeroHeight       ZeroHeight
-		thickTop         ThickTop
-		thickBottom      ThickBottom
-	)
-
-	if err := f.GetSheetFormatPr(sheet,
-		&baseColWidth,
-		&defaultColWidth,
-		&defaultRowHeight,
-		&customHeight,
-		&zeroHeight,
-		&thickTop,
-		&thickBottom,
-	); err != nil {
-		fmt.Println(err)
-	}
-	fmt.Println("Defaults:")
-	fmt.Println("- baseColWidth:", baseColWidth)
-	fmt.Println("- defaultColWidth:", defaultColWidth)
-	fmt.Println("- defaultRowHeight:", defaultRowHeight)
-	fmt.Println("- customHeight:", customHeight)
-	fmt.Println("- zeroHeight:", zeroHeight)
-	fmt.Println("- thickTop:", thickTop)
-	fmt.Println("- thickBottom:", thickBottom)
-	// Output:
-	// Defaults:
-	// - baseColWidth: 0
-	// - defaultColWidth: 0
-	// - defaultRowHeight: 15
-	// - customHeight: false
-	// - zeroHeight: false
-	// - thickTop: false
-	// - thickBottom: false
-}
-
-func TestSheetFormatPrOptions(t *testing.T) {
-	const sheet = "Sheet1"
-
-	testData := []struct {
-		container  SheetFormatPrOptionsPtr
-		nonDefault SheetFormatPrOptions
-	}{
-		{new(BaseColWidth), BaseColWidth(1.0)},
-		{new(DefaultColWidth), DefaultColWidth(1.0)},
-		{new(DefaultRowHeight), DefaultRowHeight(1.0)},
-		{new(CustomHeight), CustomHeight(true)},
-		{new(ZeroHeight), ZeroHeight(true)},
-		{new(ThickTop), ThickTop(true)},
-		{new(ThickBottom), ThickBottom(true)},
-	}
-
-	for i, test := range testData {
-		t.Run(fmt.Sprintf("TestData%d", i), func(t *testing.T) {
-
-			opt := test.nonDefault
-			t.Logf("option %T", opt)
-
-			def := deepcopy.Copy(test.container).(SheetFormatPrOptionsPtr)
-			val1 := deepcopy.Copy(def).(SheetFormatPrOptionsPtr)
-			val2 := deepcopy.Copy(def).(SheetFormatPrOptionsPtr)
-
-			f := NewFile()
-			// Get the default value
-			assert.NoError(t, f.GetSheetFormatPr(sheet, def), opt)
-			// Get again and check
-			assert.NoError(t, f.GetSheetFormatPr(sheet, val1), opt)
-			if !assert.Equal(t, val1, def, opt) {
-				t.FailNow()
-			}
-			// Set the same value
-			assert.NoError(t, f.SetSheetFormatPr(sheet, val1), opt)
-			// Get again and check
-			assert.NoError(t, f.GetSheetFormatPr(sheet, val1), opt)
-			if !assert.Equal(t, val1, def, "%T: value should not have changed", opt) {
-				t.FailNow()
-			}
-			// Set a different value
-			assert.NoError(t, f.SetSheetFormatPr(sheet, test.nonDefault), opt)
-			assert.NoError(t, f.GetSheetFormatPr(sheet, val1), opt)
-			// Get again and compare
-			assert.NoError(t, f.GetSheetFormatPr(sheet, val2), opt)
-			if !assert.Equal(t, val1, val2, "%T: value should not have changed", opt) {
-				t.FailNow()
-			}
-			// Value should not be the same as the default
-			if !assert.NotEqual(t, def, val1, "%T: value should have changed from default", opt) {
-				t.FailNow()
-			}
-			// Restore the default value
-			assert.NoError(t, f.SetSheetFormatPr(sheet, def), opt)
-			assert.NoError(t, f.GetSheetFormatPr(sheet, val1), opt)
-			if !assert.Equal(t, def, val1) {
-				t.FailNow()
-			}
-		})
-	}
-}
-
-func TestSetSheetFormatPr(t *testing.T) {
-	f := NewFile()
-	assert.NoError(t, f.GetSheetFormatPr("Sheet1"))
-	f.Sheet["xl/worksheets/sheet1.xml"].SheetFormatPr = nil
-	assert.NoError(t, f.SetSheetFormatPr("Sheet1", BaseColWidth(1.0)))
-	// Test set formatting properties on not exists worksheet.
-	assert.EqualError(t, f.SetSheetFormatPr("SheetN"), "sheet SheetN is not exist")
-}
-
-func TestGetSheetFormatPr(t *testing.T) {
-	f := NewFile()
-	assert.NoError(t, f.GetSheetFormatPr("Sheet1"))
-	f.Sheet["xl/worksheets/sheet1.xml"].SheetFormatPr = nil
-	var (
-		baseColWidth     BaseColWidth
-		defaultColWidth  DefaultColWidth
-		defaultRowHeight DefaultRowHeight
-		customHeight     CustomHeight
-		zeroHeight       ZeroHeight
-		thickTop         ThickTop
-		thickBottom      ThickBottom
-	)
-	assert.NoError(t, f.GetSheetFormatPr("Sheet1",
-		&baseColWidth,
-		&defaultColWidth,
-		&defaultRowHeight,
-		&customHeight,
-		&zeroHeight,
-		&thickTop,
-		&thickBottom,
-	))
-	// Test get formatting properties on not exists worksheet.
-	assert.EqualError(t, f.GetSheetFormatPr("SheetN"), "sheet SheetN is not exist")
-}

+ 10 - 14
sheetview.go

@@ -1,13 +1,11 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
+// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
 // this source code is governed by a BSD-style license that can be found in
 // the LICENSE file.
 //
 // Package excelize providing a set of functions that allow you to write to
-// and read from XLSX / XLSM / XLTM files. Supports reading and writing
-// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
-// complex components by high compatibility, and provided streaming API for
-// generating or reading data from a worksheet with huge amounts of data. This
-// library needs Go version 1.15 or later.
+// and read from XLSX files. Support reads and writes XLSX file generated by
+// Microsoft Excel™ 2007 and later. Support save file without losing original
+// charts of XLSX. This library needs Go version 1.10 or later.
 
 package excelize
 
@@ -140,21 +138,21 @@ func (o *ZoomScale) getSheetViewOption(view *xlsxSheetView) {
 }
 
 // getSheetView returns the SheetView object
-func (f *File) getSheetView(sheet string, viewIndex int) (*xlsxSheetView, error) {
-	ws, err := f.workSheetReader(sheet)
+func (f *File) getSheetView(sheetName string, viewIndex int) (*xlsxSheetView, error) {
+	xlsx, err := f.workSheetReader(sheetName)
 	if err != nil {
 		return nil, err
 	}
 	if viewIndex < 0 {
-		if viewIndex < -len(ws.SheetViews.SheetView) {
+		if viewIndex < -len(xlsx.SheetViews.SheetView) {
 			return nil, fmt.Errorf("view index %d out of range", viewIndex)
 		}
-		viewIndex = len(ws.SheetViews.SheetView) + viewIndex
-	} else if viewIndex >= len(ws.SheetViews.SheetView) {
+		viewIndex = len(xlsx.SheetViews.SheetView) + viewIndex
+	} else if viewIndex >= len(xlsx.SheetViews.SheetView) {
 		return nil, fmt.Errorf("view index %d out of range", viewIndex)
 	}
 
-	return &(ws.SheetViews.SheetView[viewIndex]), err
+	return &(xlsx.SheetViews.SheetView[viewIndex]), err
 }
 
 // SetSheetViewOptions sets sheet view options. The viewIndex may be negative
@@ -169,7 +167,6 @@ func (f *File) getSheetView(sheet string, viewIndex int) (*xlsxSheetView, error)
 //    ShowRowColHeaders(bool)
 //    ZoomScale(float64)
 //    TopLeftCell(string)
-//    ShowZeros(bool)
 //
 // Example:
 //
@@ -199,7 +196,6 @@ func (f *File) SetSheetViewOptions(name string, viewIndex int, opts ...SheetView
 //    ShowRowColHeaders(bool)
 //    ZoomScale(float64)
 //    TopLeftCell(string)
-//    ShowZeros(bool)
 //
 // Example:
 //

+ 47 - 45
sheetview_test.go

@@ -1,58 +1,60 @@
-package excelize
+package excelize_test
 
 import (
 	"fmt"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
+
+	"github.com/360EntSecGroup-Skylar/excelize/v2"
 )
 
-var _ = []SheetViewOption{
-	DefaultGridColor(true),
-	RightToLeft(false),
-	ShowFormulas(false),
-	ShowGridLines(true),
-	ShowRowColHeaders(true),
-	TopLeftCell("B2"),
+var _ = []excelize.SheetViewOption{
+	excelize.DefaultGridColor(true),
+	excelize.RightToLeft(false),
+	excelize.ShowFormulas(false),
+	excelize.ShowGridLines(true),
+	excelize.ShowRowColHeaders(true),
+	excelize.TopLeftCell("B2"),
 	// SheetViewOptionPtr are also SheetViewOption
-	new(DefaultGridColor),
-	new(RightToLeft),
-	new(ShowFormulas),
-	new(ShowGridLines),
-	new(ShowRowColHeaders),
-	new(TopLeftCell),
+	new(excelize.DefaultGridColor),
+	new(excelize.RightToLeft),
+	new(excelize.ShowFormulas),
+	new(excelize.ShowGridLines),
+	new(excelize.ShowRowColHeaders),
+	new(excelize.TopLeftCell),
 }
 
-var _ = []SheetViewOptionPtr{
-	(*DefaultGridColor)(nil),
-	(*RightToLeft)(nil),
-	(*ShowFormulas)(nil),
-	(*ShowGridLines)(nil),
-	(*ShowRowColHeaders)(nil),
-	(*TopLeftCell)(nil),
+var _ = []excelize.SheetViewOptionPtr{
+	(*excelize.DefaultGridColor)(nil),
+	(*excelize.RightToLeft)(nil),
+	(*excelize.ShowFormulas)(nil),
+	(*excelize.ShowGridLines)(nil),
+	(*excelize.ShowRowColHeaders)(nil),
+	(*excelize.TopLeftCell)(nil),
 }
 
 func ExampleFile_SetSheetViewOptions() {
-	f := NewFile()
+	f := excelize.NewFile()
 	const sheet = "Sheet1"
 
 	if err := f.SetSheetViewOptions(sheet, 0,
-		DefaultGridColor(false),
-		RightToLeft(false),
-		ShowFormulas(true),
-		ShowGridLines(true),
-		ShowRowColHeaders(true),
-		ZoomScale(80),
-		TopLeftCell("C3"),
+		excelize.DefaultGridColor(false),
+		excelize.RightToLeft(false),
+		excelize.ShowFormulas(true),
+		excelize.ShowGridLines(true),
+		excelize.ShowRowColHeaders(true),
+		excelize.ZoomScale(80),
+		excelize.TopLeftCell("C3"),
 	); err != nil {
 		fmt.Println(err)
 	}
 
-	var zoomScale ZoomScale
+	var zoomScale excelize.ZoomScale
 	fmt.Println("Default:")
 	fmt.Println("- zoomScale: 80")
 
-	if err := f.SetSheetViewOptions(sheet, 0, ZoomScale(500)); err != nil {
+	if err := f.SetSheetViewOptions(sheet, 0, excelize.ZoomScale(500)); err != nil {
 		fmt.Println(err)
 	}
 
@@ -63,7 +65,7 @@ func ExampleFile_SetSheetViewOptions() {
 	fmt.Println("Used out of range value:")
 	fmt.Println("- zoomScale:", zoomScale)
 
-	if err := f.SetSheetViewOptions(sheet, 0, ZoomScale(123)); err != nil {
+	if err := f.SetSheetViewOptions(sheet, 0, excelize.ZoomScale(123)); err != nil {
 		fmt.Println(err)
 	}
 
@@ -85,18 +87,18 @@ func ExampleFile_SetSheetViewOptions() {
 }
 
 func ExampleFile_GetSheetViewOptions() {
-	f := NewFile()
+	f := excelize.NewFile()
 	const sheet = "Sheet1"
 
 	var (
-		defaultGridColor  DefaultGridColor
-		rightToLeft       RightToLeft
-		showFormulas      ShowFormulas
-		showGridLines     ShowGridLines
-		showZeros         ShowZeros
-		showRowColHeaders ShowRowColHeaders
-		zoomScale         ZoomScale
-		topLeftCell       TopLeftCell
+		defaultGridColor  excelize.DefaultGridColor
+		rightToLeft       excelize.RightToLeft
+		showFormulas      excelize.ShowFormulas
+		showGridLines     excelize.ShowGridLines
+		showZeros         excelize.ShowZeros
+		showRowColHeaders excelize.ShowRowColHeaders
+		zoomScale         excelize.ZoomScale
+		topLeftCell       excelize.TopLeftCell
 	)
 
 	if err := f.GetSheetViewOptions(sheet, 0,
@@ -122,7 +124,7 @@ func ExampleFile_GetSheetViewOptions() {
 	fmt.Println("- zoomScale:", zoomScale)
 	fmt.Println("- topLeftCell:", `"`+topLeftCell+`"`)
 
-	if err := f.SetSheetViewOptions(sheet, 0, TopLeftCell("B2")); err != nil {
+	if err := f.SetSheetViewOptions(sheet, 0, excelize.TopLeftCell("B2")); err != nil {
 		fmt.Println(err)
 	}
 
@@ -130,7 +132,7 @@ func ExampleFile_GetSheetViewOptions() {
 		fmt.Println(err)
 	}
 
-	if err := f.SetSheetViewOptions(sheet, 0, ShowGridLines(false)); err != nil {
+	if err := f.SetSheetViewOptions(sheet, 0, excelize.ShowGridLines(false)); err != nil {
 		fmt.Println(err)
 	}
 
@@ -138,7 +140,7 @@ func ExampleFile_GetSheetViewOptions() {
 		fmt.Println(err)
 	}
 
-	if err := f.SetSheetViewOptions(sheet, 0, ShowZeros(false)); err != nil {
+	if err := f.SetSheetViewOptions(sheet, 0, excelize.ShowZeros(false)); err != nil {
 		fmt.Println(err)
 	}
 
@@ -168,7 +170,7 @@ func ExampleFile_GetSheetViewOptions() {
 }
 
 func TestSheetViewOptionsErrors(t *testing.T) {
-	f := NewFile()
+	f := excelize.NewFile()
 	const sheet = "Sheet1"
 
 	assert.NoError(t, f.GetSheetViewOptions(sheet, 0))

+ 7 - 9
sparkline.go

@@ -1,13 +1,11 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
+// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
 // this source code is governed by a BSD-style license that can be found in
 // the LICENSE file.
 //
 // Package excelize providing a set of functions that allow you to write to
-// and read from XLSX / XLSM / XLTM files. Supports reading and writing
-// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
-// complex components by high compatibility, and provided streaming API for
-// generating or reading data from a worksheet with huge amounts of data. This
-// library needs Go version 1.15 or later.
+// and read from XLSX files. Support reads and writes XLSX file generated by
+// Microsoft Excel™ 2007 and later. Support save file without losing original
+// charts of XLSX. This library needs Go version 1.10 or later.
 
 package excelize
 
@@ -441,7 +439,7 @@ func (f *File) AddSparkline(sheet string, opt *SparklineOption) (err error) {
 		}
 	} else {
 		groups = &xlsxX14SparklineGroups{
-			XMLNSXM:         NameSpaceSpreadSheetExcel2006Main.Value,
+			XMLNSXM:         NameSpaceSpreadSheetExcel2006Main,
 			SparklineGroups: []*xlsxX14SparklineGroup{group},
 		}
 		if sparklineGroupsBytes, err = xml.Marshal(groups); err != nil {
@@ -455,7 +453,7 @@ func (f *File) AddSparkline(sheet string, opt *SparklineOption) (err error) {
 		}
 		ws.ExtLst.Ext = string(extBytes)
 	}
-	f.addSheetNameSpace(sheet, NameSpaceSpreadSheetX14)
+
 	return
 }
 
@@ -525,7 +523,7 @@ func (f *File) appendSparkline(ws *xlsxWorksheet, group *xlsxX14SparklineGroup,
 				return
 			}
 			groups = &xlsxX14SparklineGroups{
-				XMLNSXM: NameSpaceSpreadSheetExcel2006Main.Value,
+				XMLNSXM: NameSpaceSpreadSheetExcel2006Main,
 				Content: decodeSparklineGroups.Content + string(sparklineGroupBytes),
 			}
 			if sparklineGroupsBytes, err = xml.Marshal(groups); err != nil {

+ 2 - 2
sparkline_test.go

@@ -211,7 +211,7 @@ func TestAddSparkline(t *testing.T) {
 		Negative: true,
 	}))
 
-	// Save spreadsheet by the given path.
+	// Save xlsx file by the given path.
 	assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddSparkline.xlsx")))
 
 	// Test error exceptions
@@ -270,7 +270,7 @@ func TestAddSparkline(t *testing.T) {
 }
 
 func TestAppendSparkline(t *testing.T) {
-	// Test unsupported charset.
+	// Test unsupport charset.
 	f := NewFile()
 	ws, err := f.workSheetReader("Sheet1")
 	assert.NoError(t, err)

+ 76 - 138
stream.go

@@ -1,13 +1,11 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
+// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
 // this source code is governed by a BSD-style license that can be found in
 // the LICENSE file.
 //
 // Package excelize providing a set of functions that allow you to write to
-// and read from XLSX / XLSM / XLTM files. Supports reading and writing
-// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
-// complex components by high compatibility, and provided streaming API for
-// generating or reading data from a worksheet with huge amounts of data. This
-// library needs Go version 1.15 or later.
+// and read from XLSX files. Support reads and writes XLSX file generated by
+// Microsoft Excel™ 2007 and later. Support save file without losing original
+// charts of XLSX. This library needs Go version 1.10 or later.
 
 package excelize
 
@@ -26,24 +24,18 @@ import (
 
 // StreamWriter defined the type of stream writer.
 type StreamWriter struct {
-	File            *File
-	Sheet           string
-	SheetID         int
-	sheetWritten    bool
-	cols            string
-	worksheet       *xlsxWorksheet
-	rawData         bufferedWriter
-	mergeCellsCount int
-	mergeCells      string
-	tableParts      string
+	File       *File
+	Sheet      string
+	SheetID    int
+	worksheet  *xlsxWorksheet
+	rawData    bufferedWriter
+	tableParts string
 }
 
 // NewStreamWriter return stream writer struct by given worksheet name for
 // generate new worksheet with large amounts of data. Note that after set
 // rows, you must call the 'Flush' method to end the streaming writing
-// process and ensure that the order of line numbers is ascending, the common
-// API and stream API can't be work mixed to writing data on the worksheets,
-// you can't get cell value when in-memory chunks data over 16MB. For
+// process and ensure that the order of line numbers is ascending. For
 // example, set data for worksheet of size 102400 rows x 50 columns with
 // numbers and style:
 //
@@ -76,16 +68,9 @@ type StreamWriter struct {
 //        fmt.Println(err)
 //    }
 //
-// Set cell value and cell formula for a worksheet with stream writer:
-//
-//    err := streamWriter.SetRow("A1", []interface{}{
-//        excelize.Cell{Value: 1},
-//        excelize.Cell{Value: 2},
-//        excelize.Cell{Formula: "SUM(A1,B1)"}});
-//
 func (f *File) NewStreamWriter(sheet string) (*StreamWriter, error) {
-	sheetID := f.getSheetID(sheet)
-	if sheetID == -1 {
+	sheetID := f.GetSheetIndex(sheet)
+	if sheetID == 0 {
 		return nil, fmt.Errorf("sheet %s is not exist", sheet)
 	}
 	sw := &StreamWriter{
@@ -98,33 +83,20 @@ func (f *File) NewStreamWriter(sheet string) (*StreamWriter, error) {
 	if err != nil {
 		return nil, err
 	}
-
-	sheetPath := f.sheetMap[trimSheetName(sheet)]
-	if f.streams == nil {
-		f.streams = make(map[string]*StreamWriter)
-	}
-	f.streams[sheetPath] = sw
-
-	_, _ = sw.rawData.WriteString(XMLHeader + `<worksheet` + templateNamespaceIDMap)
-	bulkAppendFields(&sw.rawData, sw.worksheet, 2, 5)
+	sw.rawData.WriteString(XMLHeader + `<worksheet` + templateNamespaceIDMap)
+	bulkAppendFields(&sw.rawData, sw.worksheet, 1, 5)
+	sw.rawData.WriteString(`<sheetData>`)
 	return sw, err
 }
 
 // AddTable creates an Excel table for the StreamWriter using the given
 // coordinate area and format set. For example, create a table of A1:D5:
 //
-//    err := sw.AddTable("A1", "D5", "")
+//    err := sw.AddTable("A1", "D5", ``)
 //
 // Create a table of F2:H6 with format set:
 //
-//    err := sw.AddTable("F2", "H6", `{
-//        "table_name": "table",
-//        "table_style": "TableStyleMedium2",
-//        "show_first_column": true,
-//        "show_last_column": true,
-//        "show_row_stripes": false,
-//        "show_column_stripes": true
-//    }`)
+//    err := sw.AddTable("F2", "H6", `{"table_name":"table","table_style":"TableStyleMedium2","show_first_column":true,"show_last_column":true,"show_row_stripes":false,"show_column_stripes":true}`)
 //
 // Note that the table must be at least two lines including the header. The
 // header cells must contain strings and must be unique.
@@ -177,7 +149,7 @@ func (sw *StreamWriter) AddTable(hcell, vcell, format string) error {
 	}
 
 	table := xlsxTable{
-		XMLNS:       NameSpaceSpreadSheet.Value,
+		XMLNS:       NameSpaceSpreadSheet,
 		ID:          tableID,
 		Name:        name,
 		DisplayName: name,
@@ -202,7 +174,7 @@ func (sw *StreamWriter) AddTable(hcell, vcell, format string) error {
 	tableXML := strings.Replace(sheetRelationshipsTableXML, "..", "xl", -1)
 
 	// Add first table for given sheet.
-	sheetPath := sw.File.sheetMap[trimSheetName(sw.Sheet)]
+	sheetPath, _ := sw.File.sheetMap[trimSheetName(sw.Sheet)]
 	sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetPath, "xl/worksheets/") + ".rels"
 	rID := sw.File.addRels(sheetRels, SourceRelationshipTable, sheetRelationshipsTableXML, "")
 
@@ -284,7 +256,6 @@ func getRowElement(token xml.Token, hrow int) (startElement xml.StartElement, ok
 // a value.
 type Cell struct {
 	StyleID int
-	Formula string
 	Value   interface{}
 }
 
@@ -299,13 +270,7 @@ func (sw *StreamWriter) SetRow(axis string, values []interface{}) error {
 	if err != nil {
 		return err
 	}
-	if !sw.sheetWritten {
-		if len(sw.cols) > 0 {
-			sw.rawData.WriteString("<cols>" + sw.cols + "</cols>")
-		}
-		_, _ = sw.rawData.WriteString(`<sheetData>`)
-		sw.sheetWritten = true
-	}
+
 	fmt.Fprintf(&sw.rawData, `<row r="%d">`, row)
 	for i, val := range values {
 		axis, err := CoordinatesToCellName(col+i, row)
@@ -316,69 +281,20 @@ func (sw *StreamWriter) SetRow(axis string, values []interface{}) error {
 		if v, ok := val.(Cell); ok {
 			c.S = v.StyleID
 			val = v.Value
-			setCellFormula(&c, v.Formula)
 		} else if v, ok := val.(*Cell); ok && v != nil {
 			c.S = v.StyleID
 			val = v.Value
-			setCellFormula(&c, v.Formula)
 		}
 		if err = setCellValFunc(&c, val); err != nil {
-			_, _ = sw.rawData.WriteString(`</row>`)
+			sw.rawData.WriteString(`</row>`)
 			return err
 		}
 		writeCell(&sw.rawData, c)
 	}
-	_, _ = sw.rawData.WriteString(`</row>`)
+	sw.rawData.WriteString(`</row>`)
 	return sw.rawData.Sync()
 }
 
-// SetColWidth provides a function to set the width of a single column or
-// multiple columns for the the StreamWriter. Note that you must call
-// the 'SetColWidth' function before the 'SetRow' function. For example set
-// the width column B:C as 20:
-//
-//    err := streamWriter.SetColWidth(2, 3, 20)
-//
-func (sw *StreamWriter) SetColWidth(min, max int, width float64) error {
-	if sw.sheetWritten {
-		return ErrStreamSetColWidth
-	}
-	if min > TotalColumns || max > TotalColumns {
-		return ErrColumnNumber
-	}
-	if min < 1 || max < 1 {
-		return ErrColumnNumber
-	}
-	if width > MaxColumnWidth {
-		return ErrColumnWidth
-	}
-	if min > max {
-		min, max = max, min
-	}
-	sw.cols += fmt.Sprintf(`<col min="%d" max="%d" width="%f" customWidth="1"/>`, min, max, width)
-	return nil
-}
-
-// MergeCell provides a function to merge cells by a given coordinate area for
-// the StreamWriter. Don't create a merged cell that overlaps with another
-// existing merged cell.
-func (sw *StreamWriter) MergeCell(hcell, vcell string) error {
-	_, err := areaRangeToCoordinates(hcell, vcell)
-	if err != nil {
-		return err
-	}
-	sw.mergeCellsCount++
-	sw.mergeCells += fmt.Sprintf(`<mergeCell ref="%s:%s"/>`, hcell, vcell)
-	return nil
-}
-
-// setCellFormula provides a function to set formula of a cell.
-func setCellFormula(c *xlsxC, formula string) {
-	if formula != "" {
-		c.F = &xlsxF{Content: formula}
-	}
-}
-
 // setCellValFunc provides a function to set value of a cell.
 func setCellValFunc(c *xlsxC, val interface{}) (err error) {
 	switch val := val.(type) {
@@ -435,7 +351,7 @@ func setCellIntFunc(c *xlsxC, val interface{}) (err error) {
 }
 
 func writeCell(buf *bufferedWriter, c xlsxC) {
-	_, _ = buf.WriteString(`<c`)
+	buf.WriteString(`<c`)
 	if c.XMLSpace.Value != "" {
 		fmt.Fprintf(buf, ` xml:%s="%s"`, c.XMLSpace.Name.Local, c.XMLSpace.Value)
 	}
@@ -446,45 +362,36 @@ func writeCell(buf *bufferedWriter, c xlsxC) {
 	if c.T != "" {
 		fmt.Fprintf(buf, ` t="%s"`, c.T)
 	}
-	_, _ = buf.WriteString(`>`)
-	if c.F != nil {
-		_, _ = buf.WriteString(`<f>`)
-		_ = xml.EscapeText(buf, []byte(c.F.Content))
-		_, _ = buf.WriteString(`</f>`)
-	}
+	buf.WriteString(`>`)
 	if c.V != "" {
-		_, _ = buf.WriteString(`<v>`)
-		_ = xml.EscapeText(buf, []byte(c.V))
-		_, _ = buf.WriteString(`</v>`)
+		buf.WriteString(`<v>`)
+		xml.EscapeText(buf, stringToBytes(c.V))
+		buf.WriteString(`</v>`)
 	}
-	_, _ = buf.WriteString(`</c>`)
+	buf.WriteString(`</c>`)
 }
 
 // Flush ending the streaming writing process.
 func (sw *StreamWriter) Flush() error {
-	if !sw.sheetWritten {
-		_, _ = sw.rawData.WriteString(`<sheetData>`)
-		sw.sheetWritten = true
-	}
-	_, _ = sw.rawData.WriteString(`</sheetData>`)
-	bulkAppendFields(&sw.rawData, sw.worksheet, 8, 15)
-	if sw.mergeCellsCount > 0 {
-		sw.mergeCells = fmt.Sprintf(`<mergeCells count="%d">%s</mergeCells>`, sw.mergeCellsCount, sw.mergeCells)
-	}
-	_, _ = sw.rawData.WriteString(sw.mergeCells)
-	bulkAppendFields(&sw.rawData, sw.worksheet, 17, 38)
-	_, _ = sw.rawData.WriteString(sw.tableParts)
-	bulkAppendFields(&sw.rawData, sw.worksheet, 40, 40)
-	_, _ = sw.rawData.WriteString(`</worksheet>`)
+	sw.rawData.WriteString(`</sheetData>`)
+	bulkAppendFields(&sw.rawData, sw.worksheet, 7, 37)
+	sw.rawData.WriteString(sw.tableParts)
+	bulkAppendFields(&sw.rawData, sw.worksheet, 39, 39)
+	sw.rawData.WriteString(`</worksheet>`)
 	if err := sw.rawData.Flush(); err != nil {
 		return err
 	}
 
-	sheetPath := sw.File.sheetMap[trimSheetName(sw.Sheet)]
-	delete(sw.File.Sheet, sheetPath)
-	delete(sw.File.checked, sheetPath)
-	delete(sw.File.XLSX, sheetPath)
+	sheetXML := fmt.Sprintf("xl/worksheets/sheet%d.xml", sw.SheetID)
+	delete(sw.File.Sheet, sheetXML)
+	delete(sw.File.checked, sheetXML)
 
+	defer sw.rawData.Close()
+	b, err := sw.rawData.Bytes()
+	if err != nil {
+		return err
+	}
+	sw.File.XLSX[sheetXML] = b
 	return nil
 }
 
@@ -495,7 +402,7 @@ func bulkAppendFields(w io.Writer, ws *xlsxWorksheet, from, to int) {
 	enc := xml.NewEncoder(w)
 	for i := 0; i < s.NumField(); i++ {
 		if from <= i && i <= to {
-			_ = enc.Encode(s.Field(i).Interface())
+			enc.Encode(s.Field(i).Interface())
 		}
 	}
 }
@@ -535,11 +442,42 @@ func (bw *bufferedWriter) Reader() (io.Reader, error) {
 	return io.NewSectionReader(bw.tmp, 0, fi.Size()), nil
 }
 
+// Bytes returns the entire content of the bufferedWriter. If a temp file is
+// used, Bytes will efficiently allocate a buffer to prevent re-allocations.
+func (bw *bufferedWriter) Bytes() ([]byte, error) {
+	if bw.tmp == nil {
+		return bw.buf.Bytes(), nil
+	}
+
+	if err := bw.Flush(); err != nil {
+		return nil, err
+	}
+
+	var buf bytes.Buffer
+	if fi, err := bw.tmp.Stat(); err == nil {
+		if size := fi.Size() + bytes.MinRead; size > bytes.MinRead {
+			if int64(int(size)) == size {
+				buf.Grow(int(size))
+			} else {
+				return nil, bytes.ErrTooLarge
+			}
+		}
+	}
+
+	if _, err := bw.tmp.Seek(0, 0); err != nil {
+		return nil, err
+	}
+
+	_, err := buf.ReadFrom(bw.tmp)
+	return buf.Bytes(), err
+}
+
 // Sync will write the in-memory buffer to a temp file, if the in-memory
 // buffer has grown large enough. Any error will be returned.
 func (bw *bufferedWriter) Sync() (err error) {
 	// Try to use local storage
-	if bw.buf.Len() < StreamChunkSize {
+	const chunk = 1 << 24
+	if bw.buf.Len() < chunk {
 		return nil
 	}
 	if bw.tmp == nil {

+ 9 - 47
stream_test.go

@@ -26,7 +26,7 @@ func BenchmarkStreamWriter(b *testing.B) {
 		streamWriter, _ := file.NewStreamWriter("Sheet1")
 		for rowID := 10; rowID <= 110; rowID++ {
 			cell, _ := CoordinatesToCellName(1, rowID)
-			_ = streamWriter.SetRow(cell, row)
+			streamWriter.SetRow(cell, row)
 		}
 	}
 
@@ -40,7 +40,7 @@ func TestStreamWriter(t *testing.T) {
 
 	// Test max characters in a cell.
 	row := make([]interface{}, 1)
-	row[0] = strings.Repeat("c", TotalCellChars+2)
+	row[0] = strings.Repeat("c", 32769)
 	assert.NoError(t, streamWriter.SetRow("A1", row))
 
 	// Test leading and ending space(s) character characters in a cell.
@@ -55,9 +55,9 @@ func TestStreamWriter(t *testing.T) {
 	// Test set cell with style.
 	styleID, err := file.NewStyle(`{"font":{"color":"#777777"}}`)
 	assert.NoError(t, err)
-	assert.NoError(t, streamWriter.SetRow("A4", []interface{}{Cell{StyleID: styleID}, Cell{Formula: "SUM(A10,B10)"}}))
-	assert.NoError(t, streamWriter.SetRow("A5", []interface{}{&Cell{StyleID: styleID, Value: "cell"}, &Cell{Formula: "SUM(A10,B10)"}}))
-	assert.EqualError(t, streamWriter.SetRow("A6", []interface{}{time.Now()}), ErrToExcelTime.Error())
+	assert.NoError(t, streamWriter.SetRow("A4", []interface{}{Cell{StyleID: styleID}}))
+	assert.NoError(t, streamWriter.SetRow("A5", []interface{}{&Cell{StyleID: styleID, Value: "cell"}}))
+	assert.EqualError(t, streamWriter.SetRow("A6", []interface{}{time.Now()}), "only UTC time expected")
 
 	for rowID := 10; rowID <= 51200; rowID++ {
 		row := make([]interface{}, 50)
@@ -69,17 +69,14 @@ func TestStreamWriter(t *testing.T) {
 	}
 
 	assert.NoError(t, streamWriter.Flush())
-	// Save spreadsheet by the given path.
+	// Save xlsx file by the given path.
 	assert.NoError(t, file.SaveAs(filepath.Join("test", "TestStreamWriter.xlsx")))
 
-	// Test set cell column overflow.
-	assert.EqualError(t, streamWriter.SetRow("XFD1", []interface{}{"A", "B", "C"}), ErrColumnNumber.Error())
-
 	// Test close temporary file error.
 	file = NewFile()
 	streamWriter, err = file.NewStreamWriter("Sheet1")
 	assert.NoError(t, err)
-	for rowID := 10; rowID <= 25600; rowID++ {
+	for rowID := 10; rowID <= 51200; rowID++ {
 		row := make([]interface{}, 50)
 		for colID := 0; colID < 50; colID++ {
 			row[colID] = rand.Intn(640000)
@@ -94,37 +91,14 @@ func TestStreamWriter(t *testing.T) {
 	assert.NoError(t, err)
 	_, err = streamWriter.rawData.Reader()
 	assert.NoError(t, err)
-	assert.NoError(t, streamWriter.rawData.tmp.Close())
 	assert.NoError(t, os.Remove(streamWriter.rawData.tmp.Name()))
 
-	// Test unsupported charset
+	// Test unsupport charset
 	file = NewFile()
 	delete(file.Sheet, "xl/worksheets/sheet1.xml")
 	file.XLSX["xl/worksheets/sheet1.xml"] = MacintoshCyrillicCharset
-	_, err = file.NewStreamWriter("Sheet1")
-	assert.EqualError(t, err, "xml decode error: XML syntax error on line 1: invalid UTF-8")
-
-	// Test read cell.
-	file = NewFile()
 	streamWriter, err = file.NewStreamWriter("Sheet1")
-	assert.NoError(t, err)
-	assert.NoError(t, streamWriter.SetRow("A1", []interface{}{Cell{StyleID: styleID, Value: "Data"}}))
-	assert.NoError(t, streamWriter.Flush())
-	cellValue, err := file.GetCellValue("Sheet1", "A1")
-	assert.NoError(t, err)
-	assert.Equal(t, "Data", cellValue)
-}
-
-func TestStreamSetColWidth(t *testing.T) {
-	file := NewFile()
-	streamWriter, err := file.NewStreamWriter("Sheet1")
-	assert.NoError(t, err)
-	assert.NoError(t, streamWriter.SetColWidth(3, 2, 20))
-	assert.EqualError(t, streamWriter.SetColWidth(0, 3, 20), ErrColumnNumber.Error())
-	assert.EqualError(t, streamWriter.SetColWidth(TotalColumns+1, 3, 20), ErrColumnNumber.Error())
-	assert.EqualError(t, streamWriter.SetColWidth(1, 3, MaxColumnWidth+1), ErrColumnWidth.Error())
-	assert.NoError(t, streamWriter.SetRow("A1", []interface{}{"A", "B", "C"}))
-	assert.EqualError(t, streamWriter.SetColWidth(2, 3, 20), ErrStreamSetColWidth.Error())
+	assert.EqualError(t, err, "xml decode error: XML syntax error on line 1: invalid UTF-8")
 }
 
 func TestStreamTable(t *testing.T) {
@@ -159,18 +133,6 @@ func TestStreamTable(t *testing.T) {
 	assert.EqualError(t, streamWriter.AddTable("A1", "B", `{}`), `cannot convert cell "B" to coordinates: invalid cell name "B"`)
 }
 
-func TestStreamMergeCells(t *testing.T) {
-	file := NewFile()
-	streamWriter, err := file.NewStreamWriter("Sheet1")
-	assert.NoError(t, err)
-	assert.NoError(t, streamWriter.MergeCell("A1", "D1"))
-	// Test merge cells with illegal cell coordinates.
-	assert.EqualError(t, streamWriter.MergeCell("A", "D1"), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
-	assert.NoError(t, streamWriter.Flush())
-	// Save spreadsheet by the given path.
-	assert.NoError(t, file.SaveAs(filepath.Join("test", "TestStreamMergeCells.xlsx")))
-}
-
 func TestNewStreamWriter(t *testing.T) {
 	// Test error exceptions
 	file := NewFile()

+ 108 - 370
styles.go

@@ -1,13 +1,11 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
+// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
 // this source code is governed by a BSD-style license that can be found in
 // the LICENSE file.
 //
 // Package excelize providing a set of functions that allow you to write to
-// and read from XLSX / XLSM / XLTM files. Supports reading and writing
-// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
-// complex components by high compatibility, and provided streaming API for
-// generating or reading data from a worksheet with huge amounts of data. This
-// library needs Go version 1.15 or later.
+// and read from XLSX files. Support reads and writes XLSX file generated by
+// Microsoft Excel™ 2007 and later. Support save file without losing original
+// charts of XLSX. This library needs Go version 1.10 or later.
 
 package excelize
 
@@ -20,15 +18,13 @@ import (
 	"io"
 	"log"
 	"math"
-	"reflect"
-	"regexp"
 	"strconv"
 	"strings"
 )
 
 // Excel styles can reference number formats that are built-in, all of which
-// have an id less than 164. Note that this number format code list is under
-// English localization.
+// have an id less than 164. This is a possibly incomplete list comprised of
+// as many of them as I could find.
 var builtInNumFmt = map[int]string{
 	0:  "general",
 	1:  "0",
@@ -46,9 +42,9 @@ var builtInNumFmt = map[int]string{
 	17: "mmm-yy",
 	18: "h:mm am/pm",
 	19: "h:mm:ss am/pm",
-	20: "hh:mm",
-	21: "hh:mm:ss",
-	22: "m/d/yy hh:mm",
+	20: "h:mm",
+	21: "h:mm:ss",
+	22: "m/d/yy h:mm",
 	37: "#,##0 ;(#,##0)",
 	38: "#,##0 ;[red](#,##0)",
 	39: "#,##0.00;(#,##0.00)",
@@ -756,7 +752,7 @@ var currencyNumFmt = map[int]string{
 
 // builtInNumFmtFunc defined the format conversion functions map. Partial format
 // code doesn't support currently and will return original string.
-var builtInNumFmtFunc = map[int]func(v string, format string) string{
+var builtInNumFmtFunc = map[int]func(i int, v string) string{
 	0:  formatToString,
 	1:  formatToInt,
 	2:  formatToFloat,
@@ -848,14 +844,14 @@ var criteriaType = map[string]string{
 
 // formatToString provides a function to return original string by given
 // built-in number formats code and cell string.
-func formatToString(v string, format string) string {
+func formatToString(i int, v string) string {
 	return v
 }
 
 // formatToInt provides a function to convert original string to integer
 // format as string type by given built-in number formats code and cell
 // string.
-func formatToInt(v string, format string) string {
+func formatToInt(i int, v string) string {
 	f, err := strconv.ParseFloat(v, 64)
 	if err != nil {
 		return v
@@ -866,7 +862,7 @@ func formatToInt(v string, format string) string {
 // formatToFloat provides a function to convert original string to float
 // format as string type by given built-in number formats code and cell
 // string.
-func formatToFloat(v string, format string) string {
+func formatToFloat(i int, v string) string {
 	f, err := strconv.ParseFloat(v, 64)
 	if err != nil {
 		return v
@@ -876,7 +872,7 @@ func formatToFloat(v string, format string) string {
 
 // formatToA provides a function to convert original string to special format
 // as string type by given built-in number formats code and cell string.
-func formatToA(v string, format string) string {
+func formatToA(i int, v string) string {
 	f, err := strconv.ParseFloat(v, 64)
 	if err != nil {
 		return v
@@ -891,7 +887,7 @@ func formatToA(v string, format string) string {
 
 // formatToB provides a function to convert original string to special format
 // as string type by given built-in number formats code and cell string.
-func formatToB(v string, format string) string {
+func formatToB(i int, v string) string {
 	f, err := strconv.ParseFloat(v, 64)
 	if err != nil {
 		return v
@@ -904,18 +900,18 @@ func formatToB(v string, format string) string {
 
 // formatToC provides a function to convert original string to special format
 // as string type by given built-in number formats code and cell string.
-func formatToC(v string, format string) string {
+func formatToC(i int, v string) string {
 	f, err := strconv.ParseFloat(v, 64)
 	if err != nil {
 		return v
 	}
 	f = f * 100
-	return fmt.Sprintf("%.f%%", f)
+	return fmt.Sprintf("%d%%", int(f))
 }
 
 // formatToD provides a function to convert original string to special format
 // as string type by given built-in number formats code and cell string.
-func formatToD(v string, format string) string {
+func formatToD(i int, v string) string {
 	f, err := strconv.ParseFloat(v, 64)
 	if err != nil {
 		return v
@@ -926,7 +922,7 @@ func formatToD(v string, format string) string {
 
 // formatToE provides a function to convert original string to special format
 // as string type by given built-in number formats code and cell string.
-func formatToE(v string, format string) string {
+func formatToE(i int, v string) string {
 	f, err := strconv.ParseFloat(v, 64)
 	if err != nil {
 		return v
@@ -945,41 +941,15 @@ func formatToE(v string, format string) string {
 // arbitrary characters unused in Excel Date formats, and then at the end,
 // turn them to what they should actually be. Based off:
 // http://www.ozgrid.com/Excel/CustomFormats.htm
-func parseTime(v string, format string) string {
-	var (
-		f     float64
-		err   error
-		goFmt string
-	)
-	f, err = strconv.ParseFloat(v, 64)
+func parseTime(i int, v string) string {
+	f, err := strconv.ParseFloat(v, 64)
 	if err != nil {
 		return v
 	}
 	val := timeFromExcelTime(f, false)
-
-	if format == "" {
-		return v
-	}
-
-	goFmt = format
-
-	if strings.Contains(goFmt, "[") {
-		var re = regexp.MustCompile(`\[.+\]`)
-		goFmt = re.ReplaceAllLiteralString(goFmt, "")
-	}
-
-	// use only first variant
-	if strings.Contains(goFmt, ";") {
-		goFmt = goFmt[:strings.IndexByte(goFmt, ';')]
-	}
+	format := builtInNumFmt[i]
 
 	replacements := []struct{ xltime, gotime string }{
-		{"YYYY", "2006"},
-		{"YY", "06"},
-		{"MM", "01"},
-		{"M", "1"},
-		{"DD", "02"},
-		{"D", "2"},
 		{"yyyy", "2006"},
 		{"yy", "06"},
 		{"mmmm", "%%%%"},
@@ -989,69 +959,38 @@ func parseTime(v string, format string) string {
 		{"mmm", "Jan"},
 		{"mmss", "0405"},
 		{"ss", "05"},
-		{"s", "5"},
 		{"mm:", "04:"},
 		{":mm", ":04"},
-		{"m:", "4:"},
-		{":m", ":4"},
 		{"mm", "01"},
 		{"am/pm", "pm"},
 		{"m/", "1/"},
 		{"%%%%", "January"},
 		{"&&&&", "Monday"},
 	}
-
-	replacementsGlobal := []struct{ xltime, gotime string }{
-		{"\\-", "-"},
-		{"\\ ", " "},
-		{"\\.", "."},
-		{"\\", ""},
-	}
 	// It is the presence of the "am/pm" indicator that determines if this is
 	// a 12 hour or 24 hours time format, not the number of 'h' characters.
-	var padding bool
-	if val.Hour() == 0 && !strings.Contains(format, "hh") && !strings.Contains(format, "HH") {
-		padding = true
-	}
 	if is12HourTime(format) {
-		goFmt = strings.Replace(goFmt, "hh", "3", 1)
-		goFmt = strings.Replace(goFmt, "h", "3", 1)
-		goFmt = strings.Replace(goFmt, "HH", "3", 1)
-		goFmt = strings.Replace(goFmt, "H", "3", 1)
+		format = strings.Replace(format, "hh", "03", 1)
+		format = strings.Replace(format, "h", "3", 1)
 	} else {
-		goFmt = strings.Replace(goFmt, "hh", "15", 1)
-		goFmt = strings.Replace(goFmt, "HH", "15", 1)
-		if 0 < val.Hour() && val.Hour() < 12 {
-			goFmt = strings.Replace(goFmt, "h", "3", 1)
-			goFmt = strings.Replace(goFmt, "H", "3", 1)
-		} else {
-			goFmt = strings.Replace(goFmt, "h", "15", 1)
-			goFmt = strings.Replace(goFmt, "H", "15", 1)
-		}
+		format = strings.Replace(format, "hh", "15", 1)
+		format = strings.Replace(format, "h", "15", 1)
 	}
-
 	for _, repl := range replacements {
-		goFmt = strings.Replace(goFmt, repl.xltime, repl.gotime, 1)
-	}
-	for _, repl := range replacementsGlobal {
-		goFmt = strings.Replace(goFmt, repl.xltime, repl.gotime, -1)
+		format = strings.Replace(format, repl.xltime, repl.gotime, 1)
 	}
 	// If the hour is optional, strip it out, along with the possible dangling
 	// colon that would remain.
 	if val.Hour() < 1 {
-		goFmt = strings.Replace(goFmt, "]:", "]", 1)
-		goFmt = strings.Replace(goFmt, "[03]", "", 1)
-		goFmt = strings.Replace(goFmt, "[3]", "", 1)
-		goFmt = strings.Replace(goFmt, "[15]", "", 1)
+		format = strings.Replace(format, "]:", "]", 1)
+		format = strings.Replace(format, "[03]", "", 1)
+		format = strings.Replace(format, "[3]", "", 1)
+		format = strings.Replace(format, "[15]", "", 1)
 	} else {
-		goFmt = strings.Replace(goFmt, "[3]", "3", 1)
-		goFmt = strings.Replace(goFmt, "[15]", "15", 1)
+		format = strings.Replace(format, "[3]", "3", 1)
+		format = strings.Replace(format, "[15]", "15", 1)
 	}
-	s := val.Format(goFmt)
-	if padding {
-		s = strings.Replace(s, "00:", "0:", 1)
-	}
-	return s
+	return val.Format(format)
 }
 
 // is12HourTime checks whether an Excel time format string is a 12 hours form.
@@ -1080,7 +1019,7 @@ func (f *File) stylesReader() *xlsxStyleSheet {
 func (f *File) styleSheetWriter() {
 	if f.Styles != nil {
 		output, _ := xml.Marshal(f.Styles)
-		f.saveFileList("xl/styles.xml", f.replaceNameSpaceBytes("xl/styles.xml", output))
+		f.saveFileList("xl/styles.xml", replaceRelationshipsNameSpaceBytes(output))
 	}
 }
 
@@ -1089,32 +1028,18 @@ func (f *File) styleSheetWriter() {
 func (f *File) sharedStringsWriter() {
 	if f.SharedStrings != nil {
 		output, _ := xml.Marshal(f.SharedStrings)
-		f.saveFileList("xl/sharedStrings.xml", f.replaceNameSpaceBytes("xl/sharedStrings.xml", output))
+		f.saveFileList("xl/sharedStrings.xml", replaceRelationshipsNameSpaceBytes(output))
 	}
 }
 
 // parseFormatStyleSet provides a function to parse the format settings of the
 // cells and conditional formats.
-func parseFormatStyleSet(style interface{}) (*Style, error) {
-	fs := Style{}
-	var err error
-	switch v := style.(type) {
-	case string:
-		err = json.Unmarshal([]byte(v), &fs)
-	case *Style:
-		fs = *v
-	default:
-		err = errors.New("invalid parameter type")
+func parseFormatStyleSet(style string) (*Style, error) {
+	format := Style{
+		DecimalPlaces: 2,
 	}
-	if fs.Font != nil {
-		if len(fs.Font.Family) > MaxFontFamilyLength {
-			return &fs, errors.New("the length of the font family name must be smaller than or equal to 31")
-		}
-		if fs.Font.Size > MaxFontSize {
-			return &fs, errors.New("font size must be between 1 and 409 points")
-		}
-	}
-	return &fs, err
+	err := json.Unmarshal([]byte(style), &format)
+	return &format, err
 }
 
 // NewStyle provides a function to create the style for cells by given JSON or
@@ -1983,124 +1908,42 @@ func (f *File) NewStyle(style interface{}) (int, error) {
 	var fs *Style
 	var err error
 	var cellXfsID, fontID, borderID, fillID int
-	fs, err = parseFormatStyleSet(style)
-	if err != nil {
-		return cellXfsID, err
-	}
-	if fs.DecimalPlaces == 0 {
-		fs.DecimalPlaces = 2
+	switch v := style.(type) {
+	case string:
+		fs, err = parseFormatStyleSet(v)
+		if err != nil {
+			return cellXfsID, err
+		}
+	case *Style:
+		fs = v
+	default:
+		return cellXfsID, errors.New("invalid parameter type")
 	}
 	s := f.stylesReader()
-	// check given style already exist.
-	if cellXfsID = f.getStyleID(s, fs); cellXfsID != -1 {
-		return cellXfsID, err
-	}
-
-	numFmtID := newNumFmt(s, fs)
+	numFmtID := setNumFmt(s, fs)
 
 	if fs.Font != nil {
-		fontID = f.getFontID(s, fs)
-		if fontID == -1 {
-			s.Fonts.Count++
-			s.Fonts.Font = append(s.Fonts.Font, f.newFont(fs))
-			fontID = s.Fonts.Count - 1
-		}
+		s.Fonts.Count++
+		s.Fonts.Font = append(s.Fonts.Font, f.setFont(fs))
+		fontID = s.Fonts.Count - 1
 	}
 
-	borderID = getBorderID(s, fs)
-	if borderID == -1 {
-		if len(fs.Border) == 0 {
-			borderID = 0
-		} else {
-			s.Borders.Count++
-			s.Borders.Border = append(s.Borders.Border, newBorders(fs))
-			borderID = s.Borders.Count - 1
-		}
-	}
+	s.Borders.Count++
+	s.Borders.Border = append(s.Borders.Border, setBorders(fs))
+	borderID = s.Borders.Count - 1
 
-	if fillID = getFillID(s, fs); fillID == -1 {
-		if fill := newFills(fs, true); fill != nil {
-			s.Fills.Count++
-			s.Fills.Fill = append(s.Fills.Fill, fill)
-			fillID = s.Fills.Count - 1
-		} else {
-			fillID = 0
-		}
+	if fill := setFills(fs, true); fill != nil {
+		s.Fills.Count++
+		s.Fills.Fill = append(s.Fills.Fill, fill)
+		fillID = s.Fills.Count - 1
 	}
 
-	applyAlignment, alignment := fs.Alignment != nil, newAlignment(fs)
-	applyProtection, protection := fs.Protection != nil, newProtection(fs)
+	applyAlignment, alignment := fs.Alignment != nil, setAlignment(fs)
+	applyProtection, protection := fs.Protection != nil, setProtection(fs)
 	cellXfsID = setCellXfs(s, fontID, numFmtID, fillID, borderID, applyAlignment, applyProtection, alignment, protection)
 	return cellXfsID, nil
 }
 
-var getXfIDFuncs = map[string]func(int, xlsxXf, *Style) bool{
-	"numFmt": func(numFmtID int, xf xlsxXf, style *Style) bool {
-		if style.NumFmt == 0 && style.CustomNumFmt == nil && numFmtID == -1 {
-			return xf.NumFmtID != nil || *xf.NumFmtID == 0
-		}
-		if style.NegRed || style.Lang != "" || style.DecimalPlaces != 2 {
-			return false
-		}
-		return xf.NumFmtID != nil && *xf.NumFmtID == numFmtID
-	},
-	"font": func(fontID int, xf xlsxXf, style *Style) bool {
-		if style.Font == nil {
-			return (xf.FontID == nil || *xf.FontID == 0) && (xf.ApplyFont == nil || !*xf.ApplyFont)
-		}
-		return xf.FontID != nil && *xf.FontID == fontID && xf.ApplyFont != nil && *xf.ApplyFont
-	},
-	"fill": func(fillID int, xf xlsxXf, style *Style) bool {
-		if style.Fill.Type == "" {
-			return (xf.FillID == nil || *xf.FillID == 0) && (xf.ApplyFill == nil || !*xf.ApplyFill)
-		}
-		return xf.FillID != nil && *xf.FillID == fillID && xf.ApplyFill != nil && *xf.ApplyFill
-	},
-	"border": func(borderID int, xf xlsxXf, style *Style) bool {
-		if len(style.Border) == 0 {
-			return (xf.BorderID == nil || *xf.BorderID == 0) && (xf.ApplyBorder == nil || !*xf.ApplyBorder)
-		}
-		return xf.BorderID != nil && *xf.BorderID == borderID && xf.ApplyBorder != nil && *xf.ApplyBorder
-	},
-	"alignment": func(ID int, xf xlsxXf, style *Style) bool {
-		if style.Alignment == nil {
-			return xf.ApplyAlignment == nil || !*xf.ApplyAlignment
-		}
-		return reflect.DeepEqual(xf.Alignment, newAlignment(style))
-	},
-	"protection": func(ID int, xf xlsxXf, style *Style) bool {
-		if style.Protection == nil {
-			return xf.ApplyProtection == nil || !*xf.ApplyProtection
-		}
-		return reflect.DeepEqual(xf.Protection, newProtection(style)) && xf.ApplyProtection != nil && *xf.ApplyProtection
-	},
-}
-
-// getStyleID provides a function to get styleID by given style. If given
-// style is not exist, will return -1.
-func (f *File) getStyleID(ss *xlsxStyleSheet, style *Style) (styleID int) {
-	styleID = -1
-	if ss.CellXfs == nil {
-		return
-	}
-	numFmtID, borderID, fillID, fontID := getNumFmtID(ss, style), getBorderID(ss, style), getFillID(ss, style), f.getFontID(ss, style)
-	if style.CustomNumFmt != nil {
-		numFmtID = getCustomNumFmtID(ss, style)
-	}
-	for xfID, xf := range ss.CellXfs.Xf {
-		if getXfIDFuncs["numFmt"](numFmtID, xf, style) &&
-			getXfIDFuncs["font"](fontID, xf, style) &&
-			getXfIDFuncs["fill"](fillID, xf, style) &&
-			getXfIDFuncs["border"](borderID, xf, style) &&
-			getXfIDFuncs["alignment"](0, xf, style) &&
-			getXfIDFuncs["protection"](0, xf, style) {
-			styleID = xfID
-			return
-		}
-	}
-	return
-}
-
 // NewConditionalStyle provides a function to create style for conditional
 // format by given style format. The parameters are the same as function
 // NewStyle(). Note that the color field uses RGB color code and only support
@@ -2112,16 +1955,16 @@ func (f *File) NewConditionalStyle(style string) (int, error) {
 		return 0, err
 	}
 	dxf := dxf{
-		Fill: newFills(fs, false),
+		Fill: setFills(fs, false),
 	}
 	if fs.Alignment != nil {
-		dxf.Alignment = newAlignment(fs)
+		dxf.Alignment = setAlignment(fs)
 	}
 	if len(fs.Border) > 0 {
-		dxf.Border = newBorders(fs)
+		dxf.Border = setBorders(fs)
 	}
 	if fs.Font != nil {
-		dxf.Font = f.newFont(fs)
+		dxf.Font = f.setFont(fs)
 	}
 	dxfStr, _ := xml.Marshal(dxf)
 	if s.Dxfs == nil {
@@ -2157,25 +2000,9 @@ func (f *File) readDefaultFont() *xlsxFont {
 	return s.Fonts.Font[0]
 }
 
-// getFontID provides a function to get font ID.
-// If given font is not exist, will return -1.
-func (f *File) getFontID(styleSheet *xlsxStyleSheet, style *Style) (fontID int) {
-	fontID = -1
-	if styleSheet.Fonts == nil || style.Font == nil {
-		return
-	}
-	for idx, fnt := range styleSheet.Fonts.Font {
-		if reflect.DeepEqual(*fnt, *f.newFont(style)) {
-			fontID = idx
-			return
-		}
-	}
-	return
-}
-
-// newFont provides a function to add font style by given cell format
+// setFont provides a function to add font style by given cell format
 // settings.
-func (f *File) newFont(style *Style) *xlsxFont {
+func (f *File) setFont(style *Style) *xlsxFont {
 	fontUnderlineType := map[string]string{"single": "single", "double": "double"}
 	if style.Font.Size < 1 {
 		style.Font.Size = 11
@@ -2190,16 +2017,17 @@ func (f *File) newFont(style *Style) *xlsxFont {
 		Family: &attrValInt{Val: intPtr(2)},
 	}
 	if style.Font.Bold {
-		fnt.B = &attrValBool{Val: &style.Font.Bold}
+		fnt.B = &style.Font.Bold
 	}
 	if style.Font.Italic {
-		fnt.I = &attrValBool{Val: &style.Font.Italic}
+		fnt.I = &style.Font.Italic
 	}
 	if *fnt.Name.Val == "" {
 		*fnt.Name.Val = f.GetDefaultFont()
 	}
 	if style.Font.Strike {
-		fnt.Strike = &attrValBool{Val: &style.Font.Strike}
+		strike := true
+		fnt.Strike = &strike
 	}
 	val, ok := fontUnderlineType[style.Font.Underline]
 	if ok {
@@ -2208,30 +2036,9 @@ func (f *File) newFont(style *Style) *xlsxFont {
 	return &fnt
 }
 
-// getNumFmtID provides a function to get number format code ID.
-// If given number format code is not exist, will return -1.
-func getNumFmtID(styleSheet *xlsxStyleSheet, style *Style) (numFmtID int) {
-	numFmtID = -1
-	if styleSheet.NumFmts == nil {
-		return
-	}
-	if _, ok := builtInNumFmt[style.NumFmt]; ok {
-		return style.NumFmt
-	}
-	if fmtCode, ok := currencyNumFmt[style.NumFmt]; ok {
-		for _, numFmt := range styleSheet.NumFmts.NumFmt {
-			if numFmt.FormatCode == fmtCode {
-				numFmtID = numFmt.NumFmtID
-				return
-			}
-		}
-	}
-	return
-}
-
-// newNumFmt provides a function to check if number format code in the range
+// setNumFmt provides a function to check if number format code in the range
 // of built-in values.
-func newNumFmt(styleSheet *xlsxStyleSheet, style *Style) int {
+func setNumFmt(styleSheet *xlsxStyleSheet, style *Style) int {
 	dp := "0."
 	numFmtID := 164 // Default custom number format code from 164.
 	if style.DecimalPlaces < 0 || style.DecimalPlaces > 30 {
@@ -2241,9 +2048,6 @@ func newNumFmt(styleSheet *xlsxStyleSheet, style *Style) int {
 		dp += "0"
 	}
 	if style.CustomNumFmt != nil {
-		if customNumFmtID := getCustomNumFmtID(styleSheet, style); customNumFmtID != -1 {
-			return customNumFmtID
-		}
 		return setCustomNumFmt(styleSheet, style)
 	}
 	_, ok := builtInNumFmt[style.NumFmt]
@@ -2283,7 +2087,6 @@ func newNumFmt(styleSheet *xlsxStyleSheet, style *Style) int {
 // setCustomNumFmt provides a function to set custom number format code.
 func setCustomNumFmt(styleSheet *xlsxStyleSheet, style *Style) int {
 	nf := xlsxNumFmt{FormatCode: *style.CustomNumFmt}
-
 	if styleSheet.NumFmts != nil {
 		nf.NumFmtID = styleSheet.NumFmts.NumFmt[len(styleSheet.NumFmts.NumFmt)-1].NumFmtID + 1
 		styleSheet.NumFmts.NumFmt = append(styleSheet.NumFmts.NumFmt, &nf)
@@ -2299,22 +2102,6 @@ func setCustomNumFmt(styleSheet *xlsxStyleSheet, style *Style) int {
 	return nf.NumFmtID
 }
 
-// getCustomNumFmtID provides a function to get custom number format code ID.
-// If given custom number format code is not exist, will return -1.
-func getCustomNumFmtID(styleSheet *xlsxStyleSheet, style *Style) (customNumFmtID int) {
-	customNumFmtID = -1
-	if styleSheet.NumFmts == nil {
-		return
-	}
-	for _, numFmt := range styleSheet.NumFmts.NumFmt {
-		if style.CustomNumFmt != nil && numFmt.FormatCode == *style.CustomNumFmt {
-			customNumFmtID = numFmt.NumFmtID
-			return
-		}
-	}
-	return
-}
-
 // setLangNumFmt provides a function to set number format code with language.
 func setLangNumFmt(styleSheet *xlsxStyleSheet, style *Style) int {
 	numFmts, ok := langNumFmt[style.Lang]
@@ -2342,29 +2129,9 @@ func setLangNumFmt(styleSheet *xlsxStyleSheet, style *Style) int {
 	return nf.NumFmtID
 }
 
-// getFillID provides a function to get fill ID. If given fill is not
-// exist, will return -1.
-func getFillID(styleSheet *xlsxStyleSheet, style *Style) (fillID int) {
-	fillID = -1
-	if styleSheet.Fills == nil || style.Fill.Type == "" {
-		return
-	}
-	fills := newFills(style, true)
-	if fills == nil {
-		return
-	}
-	for idx, fill := range styleSheet.Fills.Fill {
-		if reflect.DeepEqual(fill, fills) {
-			fillID = idx
-			return
-		}
-	}
-	return
-}
-
-// newFills provides a function to add fill elements in the styles.xml by
+// setFills provides a function to add fill elements in the styles.xml by
 // given cell format settings.
-func newFills(style *Style, fg bool) *xlsxFill {
+func setFills(style *Style, fg bool) *xlsxFill {
 	var patterns = []string{
 		"none",
 		"solid",
@@ -2412,6 +2179,8 @@ func newFills(style *Style, fg bool) *xlsxFill {
 			gradient.Left = 0.5
 			gradient.Right = 0.5
 			gradient.Top = 0.5
+		default:
+			break
 		}
 		var stops []*xlsxGradientFillStop
 		for index, color := range style.Fill.Color {
@@ -2432,14 +2201,8 @@ func newFills(style *Style, fg bool) *xlsxFill {
 		var pattern xlsxPatternFill
 		pattern.PatternType = patterns[style.Fill.Pattern]
 		if fg {
-			if pattern.FgColor == nil {
-				pattern.FgColor = new(xlsxColor)
-			}
 			pattern.FgColor.RGB = getPaletteColor(style.Fill.Color[0])
 		} else {
-			if pattern.BgColor == nil {
-				pattern.BgColor = new(xlsxColor)
-			}
 			pattern.BgColor.RGB = getPaletteColor(style.Fill.Color[0])
 		}
 		fill.PatternFill = &pattern
@@ -2449,11 +2212,11 @@ func newFills(style *Style, fg bool) *xlsxFill {
 	return &fill
 }
 
-// newAlignment provides a function to formatting information pertaining to
+// setAlignment provides a function to formatting information pertaining to
 // text alignment in cells. There are a variety of choices for how text is
 // aligned both horizontally and vertically, as well as indentation settings,
 // and so on.
-func newAlignment(style *Style) *xlsxAlignment {
+func setAlignment(style *Style) *xlsxAlignment {
 	var alignment xlsxAlignment
 	if style.Alignment != nil {
 		alignment.Horizontal = style.Alignment.Horizontal
@@ -2469,36 +2232,20 @@ func newAlignment(style *Style) *xlsxAlignment {
 	return &alignment
 }
 
-// newProtection provides a function to set protection properties associated
+// setProtection provides a function to set protection properties associated
 // with the cell.
-func newProtection(style *Style) *xlsxProtection {
+func setProtection(style *Style) *xlsxProtection {
 	var protection xlsxProtection
 	if style.Protection != nil {
-		protection.Hidden = &style.Protection.Hidden
-		protection.Locked = &style.Protection.Locked
+		protection.Hidden = style.Protection.Hidden
+		protection.Locked = style.Protection.Locked
 	}
 	return &protection
 }
 
-// getBorderID provides a function to get border ID. If given border is not
-// exist, will return -1.
-func getBorderID(styleSheet *xlsxStyleSheet, style *Style) (borderID int) {
-	borderID = -1
-	if styleSheet.Borders == nil || len(style.Border) == 0 {
-		return
-	}
-	for idx, border := range styleSheet.Borders.Border {
-		if reflect.DeepEqual(*border, *newBorders(style)) {
-			borderID = idx
-			return
-		}
-	}
-	return
-}
-
-// newBorders provides a function to add border elements in the styles.xml by
+// setBorders provides a function to add border elements in the styles.xml by
 // given borders format settings.
-func newBorders(style *Style) *xlsxBorder {
+func setBorders(style *Style) *xlsxBorder {
 	var styles = []string{
 		"none",
 		"thin",
@@ -2552,29 +2299,21 @@ func newBorders(style *Style) *xlsxBorder {
 // cell.
 func setCellXfs(style *xlsxStyleSheet, fontID, numFmtID, fillID, borderID int, applyAlignment, applyProtection bool, alignment *xlsxAlignment, protection *xlsxProtection) int {
 	var xf xlsxXf
-	xf.FontID = intPtr(fontID)
+	xf.FontID = fontID
 	if fontID != 0 {
-		xf.ApplyFont = boolPtr(true)
+		xf.ApplyFont = true
 	}
-	xf.NumFmtID = intPtr(numFmtID)
+	xf.NumFmtID = numFmtID
 	if numFmtID != 0 {
-		xf.ApplyNumberFormat = boolPtr(true)
-	}
-	xf.FillID = intPtr(fillID)
-	if fillID != 0 {
-		xf.ApplyFill = boolPtr(true)
-	}
-	xf.BorderID = intPtr(borderID)
-	if borderID != 0 {
-		xf.ApplyBorder = boolPtr(true)
+		xf.ApplyNumberFormat = true
 	}
+	xf.FillID = fillID
+	xf.BorderID = borderID
 	style.CellXfs.Count++
 	xf.Alignment = alignment
-	if alignment != nil {
-		xf.ApplyAlignment = boolPtr(applyAlignment)
-	}
+	xf.ApplyAlignment = applyAlignment
 	if applyProtection {
-		xf.ApplyProtection = boolPtr(applyProtection)
+		xf.ApplyProtection = applyProtection
 		xf.Protection = protection
 	}
 	xfID := 0
@@ -2586,15 +2325,15 @@ func setCellXfs(style *xlsxStyleSheet, fontID, numFmtID, fillID, borderID int, a
 // GetCellStyle provides a function to get cell style index by given worksheet
 // name and cell coordinates.
 func (f *File) GetCellStyle(sheet, axis string) (int, error) {
-	ws, err := f.workSheetReader(sheet)
+	xlsx, err := f.workSheetReader(sheet)
 	if err != nil {
 		return 0, err
 	}
-	cellData, col, _, err := f.prepareCell(ws, sheet, axis)
+	cellData, col, _, err := f.prepareCell(xlsx, sheet, axis)
 	if err != nil {
 		return 0, err
 	}
-	return f.prepareCellStyle(ws, col, cellData.S), err
+	return f.prepareCellStyle(xlsx, col, cellData.S), err
 }
 
 // SetCellStyle provides a function to add style attribute for cells by given
@@ -2688,16 +2427,16 @@ func (f *File) SetCellStyle(sheet, hcell, vcell string, styleID int) error {
 	vcolIdx := vcol - 1
 	vrowIdx := vrow - 1
 
-	ws, err := f.workSheetReader(sheet)
+	xlsx, err := f.workSheetReader(sheet)
 	if err != nil {
 		return err
 	}
-	prepareSheetXML(ws, vcol, vrow)
-	makeContiguousColumns(ws, hrow, vrow, vcol)
+	prepareSheetXML(xlsx, vcol, vrow)
+	makeContiguousColumns(xlsx, hrow, vrow, vcol)
 
 	for r := hrowIdx; r <= vrowIdx; r++ {
 		for k := hcolIdx; k <= vcolIdx; k++ {
-			ws.SheetData.Row[r].C[k].S = styleID
+			xlsx.SheetData.Row[r].C[k].S = styleID
 		}
 	}
 	return err
@@ -2932,7 +2671,7 @@ func (f *File) SetConditionalFormat(sheet, area, formatSet string) error {
 		"expression":      drawConfFmtExp,
 	}
 
-	ws, err := f.workSheetReader(sheet)
+	xlsx, err := f.workSheetReader(sheet)
 	if err != nil {
 		return err
 	}
@@ -2954,7 +2693,7 @@ func (f *File) SetConditionalFormat(sheet, area, formatSet string) error {
 		}
 	}
 
-	ws.ConditionalFormatting = append(ws.ConditionalFormatting, &xlsxConditionalFormatting{
+	xlsx.ConditionalFormatting = append(xlsx.ConditionalFormatting, &xlsxConditionalFormatting{
 		SQRef:  area,
 		CfRule: cfRule,
 	})
@@ -3116,10 +2855,12 @@ func (f *File) themeReader() *xlsxTheme {
 		err   error
 		theme xlsxTheme
 	)
+
 	if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML("xl/theme/theme1.xml")))).
 		Decode(&theme); err != nil && err != io.EOF {
 		log.Printf("xml decoder error: %s", err)
 	}
+
 	return &theme
 }
 
@@ -3131,10 +2872,7 @@ func ThemeColor(baseColor string, tint float64) string {
 	r, _ := strconv.ParseInt(baseColor[0:2], 16, 64)
 	g, _ := strconv.ParseInt(baseColor[2:4], 16, 64)
 	b, _ := strconv.ParseInt(baseColor[4:6], 16, 64)
-	var h, s, l float64
-	if r >= 0 && r <= math.MaxUint8 && g >= 0 && g <= math.MaxUint8 && b >= 0 && b <= math.MaxUint8 {
-		h, s, l = RGBToHSL(uint8(r), uint8(g), uint8(b))
-	}
+	h, s, l := RGBToHSL(uint8(r), uint8(g), uint8(b))
 	if tint < 0 {
 		l *= (1 + tint)
 	} else {

+ 13 - 98
styles_test.go

@@ -2,9 +2,7 @@ package excelize
 
 import (
 	"fmt"
-	"math"
 	"path/filepath"
-	"strings"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -28,23 +26,18 @@ func TestStyleFill(t *testing.T) {
 	for _, testCase := range cases {
 		xl := NewFile()
 		styleID, err := xl.NewStyle(testCase.format)
-		assert.NoError(t, err)
+		if err != nil {
+			t.Fatal(err)
+		}
 
 		styles := xl.stylesReader()
 		style := styles.CellXfs.Xf[styleID]
 		if testCase.expectFill {
-			assert.NotEqual(t, *style.FillID, 0, testCase.label)
+			assert.NotEqual(t, style.FillID, 0, testCase.label)
 		} else {
-			assert.Equal(t, *style.FillID, 0, testCase.label)
+			assert.Equal(t, style.FillID, 0, testCase.label)
 		}
 	}
-	f := NewFile()
-	styleID1, err := f.NewStyle(`{"fill":{"type":"pattern","pattern":1,"color":["#000000"]}}`)
-	assert.NoError(t, err)
-	styleID2, err := f.NewStyle(`{"fill":{"type":"pattern","pattern":1,"color":["#000000"]}}`)
-	assert.NoError(t, err)
-	assert.Equal(t, styleID1, styleID2)
-	assert.NoError(t, f.SaveAs(filepath.Join("test", "TestStyleFill.xlsx")))
 }
 
 func TestSetConditionalFormat(t *testing.T) {
@@ -156,18 +149,18 @@ func TestSetConditionalFormat(t *testing.T) {
 	}}
 
 	for _, testCase := range cases {
-		f := NewFile()
+		xl := NewFile()
 		const sheet = "Sheet1"
 		const cellRange = "A1:A1"
 
-		err := f.SetConditionalFormat(sheet, cellRange, testCase.format)
+		err := xl.SetConditionalFormat(sheet, cellRange, testCase.format)
 		if err != nil {
 			t.Fatalf("%s", err)
 		}
 
-		ws, err := f.workSheetReader(sheet)
+		xlsx, err := xl.workSheetReader(sheet)
 		assert.NoError(t, err)
-		cf := ws.ConditionalFormatting
+		cf := xlsx.ConditionalFormatting
 		assert.Len(t, cf, 1, testCase.label)
 		assert.Len(t, cf[0].CfRule, 1, testCase.label)
 		assert.Equal(t, cellRange, cf[0].SQRef, testCase.label)
@@ -185,7 +178,7 @@ func TestUnsetConditionalFormat(t *testing.T) {
 	assert.NoError(t, f.UnsetConditionalFormat("Sheet1", "A1:A10"))
 	// Test unset conditional format on not exists worksheet.
 	assert.EqualError(t, f.UnsetConditionalFormat("SheetN", "A1:A10"), "sheet SheetN is not exist")
-	// Save spreadsheet by the given path.
+	// Save xlsx file by the given path.
 	assert.NoError(t, f.SaveAs(filepath.Join("test", "TestUnsetConditionalFormat.xlsx")))
 }
 
@@ -195,51 +188,13 @@ func TestNewStyle(t *testing.T) {
 	assert.NoError(t, err)
 	styles := f.stylesReader()
 	fontID := styles.CellXfs.Xf[styleID].FontID
-	font := styles.Fonts.Font[*fontID]
+	font := styles.Fonts.Font[fontID]
 	assert.Contains(t, *font.Name.Val, "Times New Roman", "Stored font should contain font name")
 	assert.Equal(t, 2, styles.CellXfs.Count, "Should have 2 styles")
 	_, err = f.NewStyle(&Style{})
 	assert.NoError(t, err)
 	_, err = f.NewStyle(Style{})
 	assert.EqualError(t, err, "invalid parameter type")
-
-	_, err = f.NewStyle(&Style{Font: &Font{Family: strings.Repeat("s", MaxFontFamilyLength+1)}})
-	assert.EqualError(t, err, "the length of the font family name must be smaller than or equal to 31")
-	_, err = f.NewStyle(&Style{Font: &Font{Size: MaxFontSize + 1}})
-	assert.EqualError(t, err, "font size must be between 1 and 409 points")
-
-	// new numeric custom style
-	fmt := "####;####"
-	f.Styles.NumFmts = nil
-	styleID, err = f.NewStyle(&Style{
-		CustomNumFmt: &fmt,
-	})
-	assert.NoError(t, err)
-	assert.Equal(t, 2, styleID)
-
-	assert.NotNil(t, f.Styles)
-	assert.NotNil(t, f.Styles.CellXfs)
-	assert.NotNil(t, f.Styles.CellXfs.Xf)
-
-	nf := f.Styles.CellXfs.Xf[styleID]
-	assert.Equal(t, 164, *nf.NumFmtID)
-
-	// new currency custom style
-	f.Styles.NumFmts = nil
-	styleID, err = f.NewStyle(&Style{
-		Lang:   "ko-kr",
-		NumFmt: 32, // must not be in currencyNumFmt
-
-	})
-	assert.NoError(t, err)
-	assert.Equal(t, 3, styleID)
-
-	assert.NotNil(t, f.Styles)
-	assert.NotNil(t, f.Styles.CellXfs)
-	assert.NotNil(t, f.Styles.CellXfs.Xf)
-
-	nf = f.Styles.CellXfs.Xf[styleID]
-	assert.Equal(t, 32, *nf.NumFmtID)
 }
 
 func TestGetDefaultFont(t *testing.T) {
@@ -259,7 +214,7 @@ func TestSetDefaultFont(t *testing.T) {
 
 func TestStylesReader(t *testing.T) {
 	f := NewFile()
-	// Test read styles with unsupported charset.
+	// Test read styles with unsupport charset.
 	f.Styles = nil
 	f.XLSX["xl/styles.xml"] = MacintoshCyrillicCharset
 	assert.EqualValues(t, new(xlsxStyleSheet), f.stylesReader())
@@ -267,7 +222,7 @@ func TestStylesReader(t *testing.T) {
 
 func TestThemeReader(t *testing.T) {
 	f := NewFile()
-	// Test read theme with unsupported charset.
+	// Test read theme with unsupport charset.
 	f.XLSX["xl/theme/theme1.xml"] = MacintoshCyrillicCharset
 	assert.EqualValues(t, new(xlsxTheme), f.themeReader())
 }
@@ -277,43 +232,3 @@ func TestSetCellStyle(t *testing.T) {
 	// Test set cell style on not exists worksheet.
 	assert.EqualError(t, f.SetCellStyle("SheetN", "A1", "A2", 1), "sheet SheetN is not exist")
 }
-
-func TestGetStyleID(t *testing.T) {
-	assert.Equal(t, -1, NewFile().getStyleID(&xlsxStyleSheet{}, nil))
-}
-
-func TestGetFillID(t *testing.T) {
-	assert.Equal(t, -1, getFillID(NewFile().stylesReader(), &Style{Fill: Fill{Type: "unknown"}}))
-}
-
-func TestParseTime(t *testing.T) {
-	assert.Equal(t, "2019", parseTime("43528", "YYYY"))
-	assert.Equal(t, "43528", parseTime("43528", ""))
-
-	assert.Equal(t, "2019-03-04 05:05:42", parseTime("43528.2123", "YYYY-MM-DD hh:mm:ss"))
-	assert.Equal(t, "2019-03-04 05:05:42", parseTime("43528.2123", "YYYY-MM-DD hh:mm:ss;YYYY-MM-DD hh:mm:ss"))
-	assert.Equal(t, "3/4/2019 5:5:42", parseTime("43528.2123", "M/D/YYYY h:m:s"))
-	assert.Equal(t, "3/4/2019 0:5:42", parseTime("43528.003958333335", "m/d/yyyy h:m:s"))
-	assert.Equal(t, "3/4/2019 0:05:42", parseTime("43528.003958333335", "M/D/YYYY h:mm:s"))
-	assert.Equal(t, "0:05", parseTime("43528.003958333335", "h:mm"))
-	assert.Equal(t, "0:0", parseTime("6.9444444444444444E-5", "h:m"))
-	assert.Equal(t, "0:00", parseTime("6.9444444444444444E-5", "h:mm"))
-	assert.Equal(t, "0:0", parseTime("6.9444444444444444E-5", "h:m"))
-	assert.Equal(t, "12:1", parseTime("0.50070601851851848", "h:m"))
-	assert.Equal(t, "23:30", parseTime("0.97952546296296295", "h:m"))
-	assert.Equal(t, "March", parseTime("43528", "mmmm"))
-	assert.Equal(t, "Monday", parseTime("43528", "dddd"))
-}
-
-func TestThemeColor(t *testing.T) {
-	for _, clr := range [][]string{
-		{"FF000000", ThemeColor("000000", -0.1)},
-		{"FF000000", ThemeColor("000000", 0)},
-		{"FF33FF33", ThemeColor("00FF00", 0.2)},
-		{"FFFFFFFF", ThemeColor("000000", 1)},
-		{"FFFFFFFF", ThemeColor(strings.Repeat(string(rune(math.MaxUint8+1)), 6), 1)},
-		{"FFFFFFFF", ThemeColor(strings.Repeat(string(rune(-1)), 6), 1)},
-	} {
-		assert.Equal(t, clr[0], clr[1])
-	}
-}

+ 41 - 74
table.go

@@ -1,13 +1,11 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
+// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
 // this source code is governed by a BSD-style license that can be found in
 // the LICENSE file.
 //
 // Package excelize providing a set of functions that allow you to write to
-// and read from XLSX / XLSM / XLTM files. Supports reading and writing
-// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
-// complex components by high compatibility, and provided streaming API for
-// generating or reading data from a worksheet with huge amounts of data. This
-// library needs Go version 1.15 or later.
+// and read from XLSX files. Support reads and writes XLSX file generated by
+// Microsoft Excel™ 2007 and later. Support save file without losing original
+// charts of XLSX. This library needs Go version 1.10 or later.
 
 package excelize
 
@@ -35,18 +33,11 @@ func parseFormatTableSet(formatSet string) (*formatTable, error) {
 // name, coordinate area and format set. For example, create a table of A1:D5
 // on Sheet1:
 //
-//    err := f.AddTable("Sheet1", "A1", "D5", "")
+//    err := f.AddTable("Sheet1", "A1", "D5", ``)
 //
 // Create a table of F2:H6 on Sheet2 with format set:
 //
-//    err := f.AddTable("Sheet2", "F2", "H6", `{
-//        "table_name": "table",
-//        "table_style": "TableStyleMedium2",
-//        "show_first_column": true,
-//        "show_last_column": true,
-//        "show_row_stripes": false,
-//        "show_column_stripes": true
-//    }`)
+//    err := f.AddTable("Sheet2", "F2", "H6", `{"table_name":"table","table_style":"TableStyleMedium2", "show_first_column":true,"show_last_column":true,"show_row_stripes":false,"show_column_stripes":true}`)
 //
 // Note that the table must be at least two lines including the header. The
 // header cells must contain strings and must be unique, and must set the
@@ -90,11 +81,9 @@ func (f *File) AddTable(sheet, hcell, vcell, format string) error {
 	// Add first table for given sheet.
 	sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(f.sheetMap[trimSheetName(sheet)], "xl/worksheets/") + ".rels"
 	rID := f.addRels(sheetRels, SourceRelationshipTable, sheetRelationshipsTableXML, "")
-	if err = f.addSheetTable(sheet, rID); err != nil {
-		return err
-	}
-	f.addSheetNameSpace(sheet, SourceRelationship)
-	if err = f.addTable(sheet, tableXML, hcol, hrow, vcol, vrow, tableID, formatSet); err != nil {
+	f.addSheetTable(sheet, rID)
+	err = f.addTable(sheet, tableXML, hcol, hrow, vcol, vrow, tableID, formatSet)
+	if err != nil {
 		return err
 	}
 	f.addContentTypePart(tableID, "table")
@@ -115,20 +104,16 @@ func (f *File) countTables() int {
 
 // addSheetTable provides a function to add tablePart element to
 // xl/worksheets/sheet%d.xml by given worksheet name and relationship index.
-func (f *File) addSheetTable(sheet string, rID int) error {
-	ws, err := f.workSheetReader(sheet)
-	if err != nil {
-		return err
-	}
+func (f *File) addSheetTable(sheet string, rID int) {
+	xlsx, _ := f.workSheetReader(sheet)
 	table := &xlsxTablePart{
 		RID: "rId" + strconv.Itoa(rID),
 	}
-	if ws.TableParts == nil {
-		ws.TableParts = &xlsxTableParts{}
+	if xlsx.TableParts == nil {
+		xlsx.TableParts = &xlsxTableParts{}
 	}
-	ws.TableParts.Count++
-	ws.TableParts.TableParts = append(ws.TableParts.TableParts, table)
-	return err
+	xlsx.TableParts.Count++
+	xlsx.TableParts.TableParts = append(xlsx.TableParts.TableParts, table)
 }
 
 // addTable provides a function to add table by given worksheet name,
@@ -160,7 +145,7 @@ func (f *File) addTable(sheet, tableXML string, x1, y1, x2, y2, i int, formatSet
 		}
 		if name == "" {
 			name = "Column" + strconv.Itoa(idx)
-			_ = f.SetCellStr(sheet, cell, name)
+			f.SetCellStr(sheet, cell, name)
 		}
 		tableColumn = append(tableColumn, &xlsxTableColumn{
 			ID:   idx,
@@ -172,7 +157,7 @@ func (f *File) addTable(sheet, tableXML string, x1, y1, x2, y2, i int, formatSet
 		name = "Table" + strconv.Itoa(i)
 	}
 	t := xlsxTable{
-		XMLNS:       NameSpaceSpreadSheet.Value,
+		XMLNS:       NameSpaceSpreadSheet,
 		ID:          i,
 		Name:        name,
 		DisplayName: name,
@@ -294,35 +279,17 @@ func (f *File) AutoFilter(sheet, hcell, vcell, format string) error {
 	}
 
 	formatSet, _ := parseAutoFilterSet(format)
-	cellStart, _ := CoordinatesToCellName(hcol, hrow, true)
-	cellEnd, _ := CoordinatesToCellName(vcol, vrow, true)
-	ref, filterDB := cellStart+":"+cellEnd, "_xlnm._FilterDatabase"
-	wb := f.workbookReader()
-	sheetID := f.GetSheetIndex(sheet)
-	filterRange := fmt.Sprintf("%s!%s", sheet, ref)
-	d := xlsxDefinedName{
-		Name:         filterDB,
-		Hidden:       true,
-		LocalSheetID: intPtr(sheetID),
-		Data:         filterRange,
-	}
-	if wb.DefinedNames == nil {
-		wb.DefinedNames = &xlsxDefinedNames{
-			DefinedName: []xlsxDefinedName{d},
-		}
-	} else {
-		var definedNameExists bool
-		for idx := range wb.DefinedNames.DefinedName {
-			definedName := wb.DefinedNames.DefinedName[idx]
-			if definedName.Name == filterDB && *definedName.LocalSheetID == sheetID && definedName.Hidden {
-				wb.DefinedNames.DefinedName[idx].Data = filterRange
-				definedNameExists = true
-			}
-		}
-		if !definedNameExists {
-			wb.DefinedNames.DefinedName = append(wb.DefinedNames.DefinedName, d)
-		}
+
+	var cellStart, cellEnd string
+	cellStart, err = CoordinatesToCellName(hcol, hrow)
+	if err != nil {
+		return err
 	}
+	cellEnd, err = CoordinatesToCellName(vcol, vrow)
+	if err != nil {
+		return err
+	}
+	ref := cellStart + ":" + cellEnd
 	refRange := vcol - hcol
 	return f.autoFilter(sheet, ref, refRange, hcol, formatSet)
 }
@@ -330,18 +297,18 @@ func (f *File) AutoFilter(sheet, hcell, vcell, format string) error {
 // autoFilter provides a function to extract the tokens from the filter
 // expression. The tokens are mainly non-whitespace groups.
 func (f *File) autoFilter(sheet, ref string, refRange, col int, formatSet *formatAutoFilter) error {
-	ws, err := f.workSheetReader(sheet)
+	xlsx, err := f.workSheetReader(sheet)
 	if err != nil {
 		return err
 	}
-	if ws.SheetPr != nil {
-		ws.SheetPr.FilterMode = true
+	if xlsx.SheetPr != nil {
+		xlsx.SheetPr.FilterMode = true
 	}
-	ws.SheetPr = &xlsxSheetPr{FilterMode: true}
+	xlsx.SheetPr = &xlsxSheetPr{FilterMode: true}
 	filter := &xlsxAutoFilter{
 		Ref: ref,
 	}
-	ws.AutoFilter = filter
+	xlsx.AutoFilter = filter
 	if formatSet.Column == "" || formatSet.Expression == "" {
 		return nil
 	}
@@ -355,9 +322,9 @@ func (f *File) autoFilter(sheet, ref string, refRange, col int, formatSet *forma
 		return fmt.Errorf("incorrect index of column '%s'", formatSet.Column)
 	}
 
-	filter.FilterColumn = append(filter.FilterColumn, &xlsxFilterColumn{
+	filter.FilterColumn = &xlsxFilterColumn{
 		ColID: offset,
-	})
+	}
 	re := regexp.MustCompile(`"(?:[^"]|"")*"|\S+`)
 	token := re.FindAllString(formatSet.Expression, -1)
 	if len(token) != 3 && len(token) != 7 {
@@ -368,7 +335,7 @@ func (f *File) autoFilter(sheet, ref string, refRange, col int, formatSet *forma
 		return err
 	}
 	f.writeAutoFilter(filter, expressions, tokens)
-	ws.AutoFilter = filter
+	xlsx.AutoFilter = filter
 	return nil
 }
 
@@ -379,14 +346,14 @@ func (f *File) writeAutoFilter(filter *xlsxAutoFilter, exp []int, tokens []strin
 		// Single equality.
 		var filters []*xlsxFilter
 		filters = append(filters, &xlsxFilter{Val: tokens[0]})
-		filter.FilterColumn[0].Filters = &xlsxFilters{Filter: filters}
+		filter.FilterColumn.Filters = &xlsxFilters{Filter: filters}
 	} else if len(exp) == 3 && exp[0] == 2 && exp[1] == 1 && exp[2] == 2 {
 		// Double equality with "or" operator.
 		filters := []*xlsxFilter{}
 		for _, v := range tokens {
 			filters = append(filters, &xlsxFilter{Val: v})
 		}
-		filter.FilterColumn[0].Filters = &xlsxFilters{Filter: filters}
+		filter.FilterColumn.Filters = &xlsxFilters{Filter: filters}
 	} else {
 		// Non default custom filter.
 		expRel := map[int]int{0: 0, 1: 2}
@@ -394,7 +361,7 @@ func (f *File) writeAutoFilter(filter *xlsxAutoFilter, exp []int, tokens []strin
 		for k, v := range tokens {
 			f.writeCustomFilter(filter, exp[expRel[k]], v)
 			if k == 1 {
-				filter.FilterColumn[0].CustomFilters.And = andRel[exp[k]]
+				filter.FilterColumn.CustomFilters.And = andRel[exp[k]]
 			}
 		}
 	}
@@ -415,12 +382,12 @@ func (f *File) writeCustomFilter(filter *xlsxAutoFilter, operator int, val strin
 		Operator: operators[operator],
 		Val:      val,
 	}
-	if filter.FilterColumn[0].CustomFilters != nil {
-		filter.FilterColumn[0].CustomFilters.CustomFilter = append(filter.FilterColumn[0].CustomFilters.CustomFilter, &customFilter)
+	if filter.FilterColumn.CustomFilters != nil {
+		filter.FilterColumn.CustomFilters.CustomFilter = append(filter.FilterColumn.CustomFilters.CustomFilter, &customFilter)
 	} else {
 		customFilters := []*xlsxCustomFilter{}
 		customFilters = append(customFilters, &customFilter)
-		filter.FilterColumn[0].CustomFilters = &xlsxCustomFilters{CustomFilter: customFilters}
+		filter.FilterColumn.CustomFilters = &xlsxCustomFilters{CustomFilter: customFilters}
 	}
 }
 

+ 1 - 7
table_test.go

@@ -29,8 +29,6 @@ func TestAddTable(t *testing.T) {
 		t.FailNow()
 	}
 
-	// Test add table in not exist worksheet.
-	assert.EqualError(t, f.AddTable("SheetN", "B26", "A21", `{}`), "sheet SheetN is not exist")
 	// Test add table with illegal formatset.
 	assert.EqualError(t, f.AddTable("Sheet1", "B26", "A21", `{x}`), "invalid character 'x' looking for beginning of object key string")
 	// Test add table with illegal cell coordinates.
@@ -95,17 +93,13 @@ func TestAutoFilterError(t *testing.T) {
 	}
 	for i, format := range formats {
 		t.Run(fmt.Sprintf("Expression%d", i+1), func(t *testing.T) {
-			err = f.AutoFilter("Sheet2", "D4", "B1", format)
+			err = f.AutoFilter("Sheet3", "D4", "B1", format)
 			if assert.Error(t, err) {
 				assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, i+1)))
 			}
 		})
 	}
 
-	assert.EqualError(t, f.autoFilter("SheetN", "A1", 1, 1, &formatAutoFilter{
-		Column:     "A",
-		Expression: "",
-	}), "sheet SheetN is not exist")
 	assert.EqualError(t, f.autoFilter("Sheet1", "A1", 1, 1, &formatAutoFilter{
 		Column:     "-",
 		Expression: "-",

+ 4 - 6
templates.go

@@ -1,13 +1,11 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
+// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
 // this source code is governed by a BSD-style license that can be found in
 // the LICENSE file.
 //
 // Package excelize providing a set of functions that allow you to write to
-// and read from XLSX / XLSM / XLTM files. Supports reading and writing
-// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
-// complex components by high compatibility, and provided streaming API for
-// generating or reading data from a worksheet with huge amounts of data. This
-// library needs Go version 1.15 or later.
+// and read from XLSX files. Support reads and writes XLSX file generated by
+// Microsoft Excel™ 2007 and later. Support save file without losing original
+// charts of XLSX. This library needs Go version 1.10 or later.
 //
 // This file contains default templates for XML files we don't yet populated
 // based on content.

BIN
test/Book1.xlsx


BIN
test/encryptAES.xlsx


BIN
test/encryptSHA1.xlsx


+ 4 - 6
vmlDrawing.go

@@ -1,13 +1,11 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
+// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
 // this source code is governed by a BSD-style license that can be found in
 // the LICENSE file.
 //
 // Package excelize providing a set of functions that allow you to write to
-// and read from XLSX / XLSM / XLTM files. Supports reading and writing
-// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
-// complex components by high compatibility, and provided streaming API for
-// generating or reading data from a worksheet with huge amounts of data. This
-// library needs Go version 1.15 or later.
+// and read from XLSX files. Support reads and writes XLSX file generated by
+// Microsoft Excel™ 2007 and later. Support save file without losing original
+// charts of XLSX. This library needs Go version 1.10 or later.
 
 package excelize
 

+ 4 - 6
xmlApp.go

@@ -1,13 +1,11 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
+// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
 // this source code is governed by a BSD-style license that can be found in
 // the LICENSE file.
 //
 // Package excelize providing a set of functions that allow you to write to
-// and read from XLSX / XLSM / XLTM files. Supports reading and writing
-// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
-// complex components by high compatibility, and provided streaming API for
-// generating or reading data from a worksheet with huge amounts of data. This
-// library needs Go version 1.15 or later.
+// and read from XLSX files. Support reads and writes XLSX file generated by
+// Microsoft Excel™ 2007 and later. Support save file without losing original
+// charts of XLSX. This library needs Go version 1.10 or later.
 
 package excelize
 

+ 4 - 6
xmlCalcChain.go

@@ -1,13 +1,11 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
+// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
 // this source code is governed by a BSD-style license that can be found in
 // the LICENSE file.
 //
 // Package excelize providing a set of functions that allow you to write to
-// and read from XLSX / XLSM / XLTM files. Supports reading and writing
-// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
-// complex components by high compatibility, and provided streaming API for
-// generating or reading data from a worksheet with huge amounts of data. This
-// library needs Go version 1.15 or later.
+// and read from XLSX files. Support reads and writes XLSX file generated by
+// Microsoft Excel™ 2007 and later. Support save file without losing original
+// charts of XLSX. This library needs Go version 1.10 or later.
 
 package excelize
 

+ 21 - 24
xmlChart.go

@@ -1,13 +1,11 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
+// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
 // this source code is governed by a BSD-style license that can be found in
 // the LICENSE file.
 //
 // Package excelize providing a set of functions that allow you to write to
-// and read from XLSX / XLSM / XLTM files. Supports reading and writing
-// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
-// complex components by high compatibility, and provided streaming API for
-// generating or reading data from a worksheet with huge amounts of data. This
-// library needs Go version 1.15 or later.
+// and read from XLSX files. Support reads and writes XLSX file generated by
+// Microsoft Excel™ 2007 and later. Support save file without losing original
+// charts of XLSX. This library needs Go version 1.10 or later.
 
 package excelize
 
@@ -18,7 +16,10 @@ import "encoding/xml"
 // charts, pie charts, scatter charts, or other types of charts.
 type xlsxChartSpace struct {
 	XMLName        xml.Name        `xml:"http://schemas.openxmlformats.org/drawingml/2006/chart chartSpace"`
+	XMLNSc         string          `xml:"xmlns:c,attr"`
 	XMLNSa         string          `xml:"xmlns:a,attr"`
+	XMLNSr         string          `xml:"xmlns:r,attr"`
+	XMLNSc16r2     string          `xml:"xmlns:c16r2,attr"`
 	Date1904       *attrValBool    `xml:"date1904"`
 	Lang           *attrValString  `xml:"lang"`
 	RoundedCorners *attrValBool    `xml:"roundedCorners"`
@@ -138,25 +139,25 @@ type aSchemeClr struct {
 }
 
 // attrValInt directly maps the val element with integer data type as an
-// attribute.
+// attribute
 type attrValInt struct {
 	Val *int `xml:"val,attr"`
 }
 
 // attrValFloat directly maps the val element with float64 data type as an
-// attribute.
+// attribute
 type attrValFloat struct {
 	Val *float64 `xml:"val,attr"`
 }
 
 // attrValBool directly maps the val element with boolean data type as an
-// attribute.
+// attribute
 type attrValBool struct {
 	Val *bool `xml:"val,attr"`
 }
 
 // attrValString directly maps the val element with string data type as an
-// attribute.
+// attribute
 type attrValString struct {
 	Val *string `xml:"val,attr"`
 }
@@ -377,7 +378,6 @@ type cChartLines struct {
 // cScaling directly maps the scaling element. This element contains
 // additional axis settings.
 type cScaling struct {
-	LogBase     *attrValFloat  `xml:"logBase"`
 	Orientation *attrValString `xml:"orientation"`
 	Max         *attrValFloat  `xml:"max"`
 	Min         *attrValFloat  `xml:"min"`
@@ -520,7 +520,6 @@ type cPageMargins struct {
 
 // formatChartAxis directly maps the format settings of the chart axis.
 type formatChartAxis struct {
-	None                bool    `json:"none"`
 	Crossing            string  `json:"crossing"`
 	MajorGridlines      bool    `json:"major_grid_lines"`
 	MinorGridlines      bool    `json:"minor_grid_lines"`
@@ -543,7 +542,6 @@ type formatChartAxis struct {
 		Italic    bool   `json:"italic"`
 		Underline bool   `json:"underline"`
 	} `json:"num_font"`
-	LogBase    float64      `json:"logbase"`
 	NameLayout formatLayout `json:"name_layout"`
 }
 
@@ -554,16 +552,15 @@ type formatChartDimension struct {
 
 // formatChart directly maps the format settings of the chart.
 type formatChart struct {
-	Type       string               `json:"type"`
-	Series     []formatChartSeries  `json:"series"`
-	Format     formatPicture        `json:"format"`
-	Dimension  formatChartDimension `json:"dimension"`
-	Legend     formatChartLegend    `json:"legend"`
-	Title      formatChartTitle     `json:"title"`
-	VaryColors bool                 `json:"vary_colors"`
-	XAxis      formatChartAxis      `json:"x_axis"`
-	YAxis      formatChartAxis      `json:"y_axis"`
-	Chartarea  struct {
+	Type      string               `json:"type"`
+	Series    []formatChartSeries  `json:"series"`
+	Format    formatPicture        `json:"format"`
+	Dimension formatChartDimension `json:"dimension"`
+	Legend    formatChartLegend    `json:"legend"`
+	Title     formatChartTitle     `json:"title"`
+	XAxis     formatChartAxis      `json:"x_axis"`
+	YAxis     formatChartAxis      `json:"y_axis"`
+	Chartarea struct {
 		Border struct {
 			None bool `json:"none"`
 		} `json:"border"`
@@ -625,7 +622,7 @@ type formatChartSeries struct {
 		Width float64 `json:"width"`
 	} `json:"line"`
 	Marker struct {
-		Symbol string  `json:"symbol"`
+		Type   string  `json:"type"`
 		Size   int     `json:"size"`
 		Width  float64 `json:"width"`
 		Border struct {

+ 23 - 23
xmlChartSheet.go

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

+ 7 - 9
xmlComments.go

@@ -1,13 +1,11 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
+// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
 // this source code is governed by a BSD-style license that can be found in
 // the LICENSE file.
 //
 // Package excelize providing a set of functions that allow you to write to
-// and read from XLSX / XLSM / XLTM files. Supports reading and writing
-// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
-// complex components by high compatibility, and provided streaming API for
-// generating or reading data from a worksheet with huge amounts of data. This
-// library needs Go version 1.15 or later.
+// and read from XLSX files. Support reads and writes XLSX file generated by
+// Microsoft Excel™ 2007 and later. Support save file without losing original
+// charts of XLSX. This library needs Go version 1.10 or later.
 
 package excelize
 
@@ -24,7 +22,7 @@ import "encoding/xml"
 // something special about the cell.
 type xlsxComments struct {
 	XMLName     xml.Name        `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main comments"`
-	Authors     xlsxAuthor      `xml:"authors"`
+	Authors     []xlsxAuthor    `xml:"authors"`
 	CommentList xlsxCommentList `xml:"commentList"`
 }
 
@@ -33,7 +31,7 @@ type xlsxComments struct {
 // have an author. The maximum length of the author string is an implementation
 // detail, but a good guideline is 255 chars.
 type xlsxAuthor struct {
-	Author []string `xml:"author"`
+	Author string `xml:"author"`
 }
 
 // xlsxCommentList (List of Comments) directly maps the xlsxCommentList element.
@@ -69,7 +67,7 @@ type xlsxText struct {
 type xlsxPhoneticRun struct {
 	Sb uint32 `xml:"sb,attr"`
 	Eb uint32 `xml:"eb,attr"`
-	T  string `xml:"t"`
+	T  string `xml:"t,attr"`
 }
 
 // formatComment directly maps the format settings of the comment.

+ 4 - 6
xmlContentTypes.go

@@ -1,13 +1,11 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
+// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
 // this source code is governed by a BSD-style license that can be found in
 // the LICENSE file.
 //
 // Package excelize providing a set of functions that allow you to write to
-// and read from XLSX / XLSM / XLTM files. Supports reading and writing
-// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
-// complex components by high compatibility, and provided streaming API for
-// generating or reading data from a worksheet with huge amounts of data. This
-// library needs Go version 1.15 or later.
+// and read from XLSX files. Support reads and writes XLSX file generated by
+// Microsoft Excel™ 2007 and later. Support save file without losing original
+// charts of XLSX. This library needs Go version 1.10 or later.
 
 package excelize
 

+ 4 - 6
xmlCore.go

@@ -1,13 +1,11 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
+// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
 // this source code is governed by a BSD-style license that can be found in
 // the LICENSE file.
 //
 // Package excelize providing a set of functions that allow you to write to
-// and read from XLSX / XLSM / XLTM files. Supports reading and writing
-// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
-// complex components by high compatibility, and provided streaming API for
-// generating or reading data from a worksheet with huge amounts of data. This
-// library needs Go version 1.15 or later.
+// and read from XLSX files. Support reads and writes XLSX file generated by
+// Microsoft Excel™ 2007 and later. Support save file without losing original
+// charts of XLSX. This library needs Go version 1.10 or later.
 
 package excelize
 

+ 4 - 6
xmlDecodeDrawing.go

@@ -1,13 +1,11 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
+// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
 // this source code is governed by a BSD-style license that can be found in
 // the LICENSE file.
 //
 // Package excelize providing a set of functions that allow you to write to
-// and read from XLSX / XLSM / XLTM files. Supports reading and writing
-// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
-// complex components by high compatibility, and provided streaming API for
-// generating or reading data from a worksheet with huge amounts of data. This
-// library needs Go version 1.15 or later.
+// and read from XLSX files. Support reads and writes XLSX file generated by
+// Microsoft Excel™ 2007 and later. Support save file without losing original
+// charts of XLSX. This library needs Go version 1.10 or later.
 
 package excelize
 

+ 17 - 47
xmlDrawing.go

@@ -1,39 +1,19 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
+// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
 // this source code is governed by a BSD-style license that can be found in
 // the LICENSE file.
 //
 // Package excelize providing a set of functions that allow you to write to
-// and read from XLSX / XLSM / XLTM files. Supports reading and writing
-// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
-// complex components by high compatibility, and provided streaming API for
-// generating or reading data from a worksheet with huge amounts of data. This
-// library needs Go version 1.15 or later.
+// and read from XLSX files. Support reads and writes XLSX file generated by
+// Microsoft Excel™ 2007 and later. Support save file without losing original
+// charts of XLSX. This library needs Go version 1.10 or later.
 
 package excelize
 
 import "encoding/xml"
 
-// Source relationship and namespace list, associated prefixes and schema in which it was
-// introduced.
-var (
-	SourceRelationship                = xml.Attr{Name: xml.Name{Local: "r", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/officeDocument/2006/relationships"}
-	SourceRelationshipCompatibility   = xml.Attr{Name: xml.Name{Local: "mc", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/markup-compatibility/2006"}
-	SourceRelationshipChart20070802   = xml.Attr{Name: xml.Name{Local: "c14", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/drawing/2007/8/2/chart"}
-	SourceRelationshipChart2014       = xml.Attr{Name: xml.Name{Local: "c16", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/drawing/2014/chart"}
-	SourceRelationshipChart201506     = xml.Attr{Name: xml.Name{Local: "c16r2", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/drawing/2015/06/chart"}
-	NameSpaceSpreadSheet              = xml.Attr{Name: xml.Name{Local: "xmlns"}, Value: "http://schemas.openxmlformats.org/spreadsheetml/2006/main"}
-	NameSpaceSpreadSheetX14           = xml.Attr{Name: xml.Name{Local: "x14", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main"}
-	NameSpaceDrawingML                = xml.Attr{Name: xml.Name{Local: "a", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/drawingml/2006/main"}
-	NameSpaceDrawingMLChart           = xml.Attr{Name: xml.Name{Local: "c", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/drawingml/2006/chart"}
-	NameSpaceDrawingMLSpreadSheet     = xml.Attr{Name: xml.Name{Local: "xdr", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"}
-	NameSpaceSpreadSheetX15           = xml.Attr{Name: xml.Name{Local: "x15", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/spreadsheetml/2010/11/main"}
-	NameSpaceSpreadSheetExcel2006Main = xml.Attr{Name: xml.Name{Local: "xne", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/excel/2006/main"}
-	NameSpaceMacExcel2008Main         = xml.Attr{Name: xml.Name{Local: "mx", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/mac/excel/2008/main"}
-)
-
 // Source relationship and namespace.
 const (
-	SourceRelationshipOfficeDocument             = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"
+	SourceRelationship                           = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
 	SourceRelationshipChart                      = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart"
 	SourceRelationshipComments                   = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments"
 	SourceRelationshipImage                      = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image"
@@ -43,15 +23,25 @@ const (
 	SourceRelationshipHyperLink                  = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink"
 	SourceRelationshipWorkSheet                  = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"
 	SourceRelationshipChartsheet                 = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chartsheet"
-	SourceRelationshipDialogsheet                = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/dialogsheet"
 	SourceRelationshipPivotTable                 = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotTable"
 	SourceRelationshipPivotCache                 = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheDefinition"
 	SourceRelationshipSharedStrings              = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings"
 	SourceRelationshipVBAProject                 = "http://schemas.microsoft.com/office/2006/relationships/vbaProject"
+	SourceRelationshipChart201506                = "http://schemas.microsoft.com/office/drawing/2015/06/chart"
+	SourceRelationshipChart20070802              = "http://schemas.microsoft.com/office/drawing/2007/8/2/chart"
+	SourceRelationshipChart2014                  = "http://schemas.microsoft.com/office/drawing/2014/chart"
+	SourceRelationshipCompatibility              = "http://schemas.openxmlformats.org/markup-compatibility/2006"
+	NameSpaceDrawingML                           = "http://schemas.openxmlformats.org/drawingml/2006/main"
+	NameSpaceDrawingMLChart                      = "http://schemas.openxmlformats.org/drawingml/2006/chart"
+	NameSpaceDrawingMLSpreadSheet                = "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"
+	NameSpaceSpreadSheet                         = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"
+	NameSpaceSpreadSheetX14                      = "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main"
+	NameSpaceSpreadSheetX15                      = "http://schemas.microsoft.com/office/spreadsheetml/2010/11/main"
+	NameSpaceSpreadSheetExcel2006Main            = "http://schemas.microsoft.com/office/excel/2006/main"
+	NameSpaceMacExcel2008Main                    = "http://schemas.microsoft.com/office/mac/excel/2008/main"
 	NameSpaceXML                                 = "http://www.w3.org/XML/1998/namespace"
 	NameSpaceXMLSchemaInstance                   = "http://www.w3.org/2001/XMLSchema-instance"
 	StrictSourceRelationship                     = "http://purl.oclc.org/ooxml/officeDocument/relationships"
-	StrictSourceRelationshipOfficeDocument       = "http://purl.oclc.org/ooxml/officeDocument/relationships/officeDocument"
 	StrictSourceRelationshipChart                = "http://purl.oclc.org/ooxml/officeDocument/relationships/chart"
 	StrictSourceRelationshipComments             = "http://purl.oclc.org/ooxml/officeDocument/relationships/comments"
 	StrictSourceRelationshipImage                = "http://purl.oclc.org/ooxml/officeDocument/relationships/image"
@@ -89,25 +79,6 @@ const (
 	ExtURIMacExcelMX             = "{64002731-A6B0-56B0-2670-7721B7C09600}"
 )
 
-// Excel specifications and limits
-const (
-	StreamChunkSize      = 1 << 24
-	MaxFontFamilyLength  = 31
-	MaxFontSize          = 409
-	MaxFileNameLength    = 207
-	MaxColumnWidth       = 255
-	MaxRowHeight         = 409
-	TotalRows            = 1048576
-	TotalColumns         = 16384
-	TotalSheetHyperlinks = 65529
-	TotalCellChars       = 32767
-	// pivotTableVersion should be greater than 3. One or more of the
-	// PivotTables chosen are created in a version of Excel earlier than
-	// Excel 2007 or in compatibility mode. Slicer can only be used with
-	// PivotTables created in Excel 2007 or a newer version of Excel.
-	pivotTableVersion = 3
-)
-
 var supportImageTypes = map[string]string{".gif": ".gif", ".jpg": ".jpeg", ".jpeg": ".jpeg", ".png": ".png", ".tif": ".tiff", ".tiff": ".tiff"}
 
 // xlsxCNvPr directly maps the cNvPr (Non-Visual Drawing Properties). This
@@ -447,7 +418,6 @@ type formatPicture struct {
 	FPrintsWithSheet bool    `json:"print_obj"`
 	FLocksWithSheet  bool    `json:"locked"`
 	NoChangeAspect   bool    `json:"lock_aspect_ratio"`
-	Autofit          bool    `json:"autofit"`
 	OffsetX          int     `json:"x_offset"`
 	OffsetY          int     `json:"y_offset"`
 	XScale           float64 `json:"x_scale"`

+ 0 - 25
xmlPivotCache.go

@@ -1,14 +1,3 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
-// this source code is governed by a BSD-style license that can be found in
-// the LICENSE file.
-//
-// Package excelize providing a set of functions that allow you to write to
-// and read from XLSX / XLSM / XLTM files. Supports reading and writing
-// spreadsheet documents generated by Microsoft 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.15 or later.
-
 package excelize
 
 import "encoding/xml"
@@ -182,20 +171,6 @@ type xlsxError struct {
 
 // xlsxString represents a character value in a PivotTable.
 type xlsxString struct {
-	V    string      `xml:"v,attr"`
-	U    bool        `xml:"u,attr,omitempty"`
-	F    bool        `xml:"f,attr,omitempty"`
-	C    string      `xml:"c,attr,omitempty"`
-	Cp   int         `xml:"cp,attr,omitempty"`
-	In   int         `xml:"in,attr,omitempty"`
-	Bc   string      `xml:"bc,attr,omitempty"`
-	Fc   string      `xml:"fc,attr,omitempty"`
-	I    bool        `xml:"i,attr,omitempty"`
-	Un   bool        `xml:"un,attr,omitempty"`
-	St   bool        `xml:"st,attr,omitempty"`
-	B    bool        `xml:"b,attr,omitempty"`
-	Tpls *xlsxTuples `xml:"tpls"`
-	X    *attrValInt `xml:"x"`
 }
 
 // xlsxDateTime represents a date-time value in the PivotTable.

+ 14 - 16
xmlPivotTable.go

@@ -1,13 +1,11 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
+// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
 // this source code is governed by a BSD-style license that can be found in
 // the LICENSE file.
 //
 // Package excelize providing a set of functions that allow you to write to
-// and read from XLSX / XLSM / XLTM files. Supports reading and writing
-// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
-// complex components by high compatibility, and provided streaming API for
-// generating or reading data from a worksheet with huge amounts of data. This
-// library needs Go version 1.15 or later.
+// and read from XLSX files. Support reads and writes XLSX file generated by
+// Microsoft Excel™ 2007 and later. Support save file without losing original
+// charts of XLSX. This library needs Go version 1.10 or later.
 
 package excelize
 
@@ -31,7 +29,7 @@ type xlsxPivotTableDefinition struct {
 	DataCaption             string                   `xml:"dataCaption,attr"`
 	GrandTotalCaption       string                   `xml:"grandTotalCaption,attr,omitempty"`
 	ErrorCaption            string                   `xml:"errorCaption,attr,omitempty"`
-	ShowError               *bool                    `xml:"showError,attr"`
+	ShowError               bool                     `xml:"showError,attr,omitempty"`
 	MissingCaption          string                   `xml:"missingCaption,attr,omitempty"`
 	ShowMissing             bool                     `xml:"showMissing,attr,omitempty"`
 	PageStyle               string                   `xml:"pageStyle,attr,omitempty"`
@@ -48,7 +46,7 @@ type xlsxPivotTableDefinition struct {
 	VisualTotals            bool                     `xml:"visualTotals,attr,omitempty"`
 	ShowMultipleLabel       bool                     `xml:"showMultipleLabel,attr,omitempty"`
 	ShowDataDropDown        bool                     `xml:"showDataDropDown,attr,omitempty"`
-	ShowDrill               *bool                    `xml:"showDrill,attr"`
+	ShowDrill               bool                     `xml:"showDrill,attr,omitempty"`
 	PrintDrill              bool                     `xml:"printDrill,attr,omitempty"`
 	ShowMemberPropertyTips  bool                     `xml:"showMemberPropertyTips,attr,omitempty"`
 	ShowDataTips            bool                     `xml:"showDataTips,attr,omitempty"`
@@ -56,15 +54,15 @@ type xlsxPivotTableDefinition struct {
 	EnableDrill             bool                     `xml:"enableDrill,attr,omitempty"`
 	EnableFieldProperties   bool                     `xml:"enableFieldProperties,attr,omitempty"`
 	PreserveFormatting      bool                     `xml:"preserveFormatting,attr,omitempty"`
-	UseAutoFormatting       *bool                    `xml:"useAutoFormatting,attr,omitempty"`
+	UseAutoFormatting       bool                     `xml:"useAutoFormatting,attr,omitempty"`
 	PageWrap                int                      `xml:"pageWrap,attr,omitempty"`
-	PageOverThenDown        *bool                    `xml:"pageOverThenDown,attr,omitempty"`
+	PageOverThenDown        bool                     `xml:"pageOverThenDown,attr,omitempty"`
 	SubtotalHiddenItems     bool                     `xml:"subtotalHiddenItems,attr,omitempty"`
-	RowGrandTotals          *bool                    `xml:"rowGrandTotals,attr,omitempty"`
-	ColGrandTotals          *bool                    `xml:"colGrandTotals,attr,omitempty"`
+	RowGrandTotals          bool                     `xml:"rowGrandTotals,attr,omitempty"`
+	ColGrandTotals          bool                     `xml:"colGrandTotals,attr,omitempty"`
 	FieldPrintTitles        bool                     `xml:"fieldPrintTitles,attr,omitempty"`
 	ItemPrintTitles         bool                     `xml:"itemPrintTitles,attr,omitempty"`
-	MergeItem               *bool                    `xml:"mergeItem,attr,omitempty"`
+	MergeItem               bool                     `xml:"mergeItem,attr,omitempty"`
 	ShowDropZones           bool                     `xml:"showDropZones,attr,omitempty"`
 	CreatedVersion          int                      `xml:"createdVersion,attr,omitempty"`
 	Indent                  int                      `xml:"indent,attr,omitempty"`
@@ -74,7 +72,7 @@ type xlsxPivotTableDefinition struct {
 	Compact                 bool                     `xml:"compact,attr"`
 	Outline                 bool                     `xml:"outline,attr"`
 	OutlineData             bool                     `xml:"outlineData,attr,omitempty"`
-	CompactData             *bool                    `xml:"compactData,attr,omitempty"`
+	CompactData             bool                     `xml:"compactData,attr,omitempty"`
 	Published               bool                     `xml:"published,attr,omitempty"`
 	GridDropZones           bool                     `xml:"gridDropZones,attr,omitempty"`
 	Immersive               bool                     `xml:"immersive,attr,omitempty"`
@@ -150,7 +148,7 @@ type xlsxPivotField struct {
 	DataSourceSort               bool               `xml:"dataSourceSort,attr,omitempty"`
 	NonAutoSortDefault           bool               `xml:"nonAutoSortDefault,attr,omitempty"`
 	RankBy                       int                `xml:"rankBy,attr,omitempty"`
-	DefaultSubtotal              *bool              `xml:"defaultSubtotal,attr,omitempty"`
+	DefaultSubtotal              bool               `xml:"defaultSubtotal,attr,omitempty"`
 	SumSubtotal                  bool               `xml:"sumSubtotal,attr,omitempty"`
 	CountASubtotal               bool               `xml:"countASubtotal,attr,omitempty"`
 	AvgSubtotal                  bool               `xml:"avgSubtotal,attr,omitempty"`
@@ -189,7 +187,7 @@ type xlsxItem struct {
 	F  bool   `xml:"f,attr,omitempty"`
 	M  bool   `xml:"m,attr,omitempty"`
 	C  bool   `xml:"c,attr,omitempty"`
-	X  *int   `xml:"x,attr,omitempty"`
+	X  int    `xml:"x,attr,omitempty"`
 	D  bool   `xml:"d,attr,omitempty"`
 	E  bool   `xml:"e,attr,omitempty"`
 }

+ 16 - 23
xmlSharedStrings.go

@@ -1,13 +1,11 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
+// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
 // this source code is governed by a BSD-style license that can be found in
 // the LICENSE file.
 //
 // Package excelize providing a set of functions that allow you to write to
-// and read from XLSX / XLSM / XLTM files. Supports reading and writing
-// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
-// complex components by high compatibility, and provided streaming API for
-// generating or reading data from a worksheet with huge amounts of data. This
-// library needs Go version 1.15 or later.
+// and read from XLSX files. Support reads and writes XLSX file generated by
+// Microsoft Excel™ 2007 and later. Support save file without losing original
+// charts of XLSX. This library needs Go version 1.10 or later.
 
 package excelize
 
@@ -38,10 +36,8 @@ type xlsxSST struct {
 // level - then the string item shall consist of multiple rich text runs which
 // collectively are used to express the string.
 type xlsxSI struct {
-	T          *xlsxT             `xml:"t,omitempty"`
-	R          []xlsxR            `xml:"r"`
-	RPh        []*xlsxPhoneticRun `xml:"rPh"`
-	PhoneticPr *xlsxPhoneticPr    `xml:"phoneticPr"`
+	T string  `xml:"t,omitempty"`
+	R []xlsxR `xml:"r"`
 }
 
 // String extracts characters from a string item.
@@ -55,10 +51,7 @@ func (x xlsxSI) String() string {
 		}
 		return rows.String()
 	}
-	if x.T != nil {
-		return x.T.Val
-	}
-	return ""
+	return x.T
 }
 
 // xlsxR represents a run of rich text. A rich text run is a region of text
@@ -73,8 +66,8 @@ type xlsxR struct {
 // xlsxT directly maps the t element in the run properties.
 type xlsxT struct {
 	XMLName xml.Name `xml:"t"`
-	Space   xml.Attr `xml:"space,attr,omitempty"`
-	Val     string   `xml:",chardata"`
+	Space   string   `xml:"xml:space,attr,omitempty"`
+	Val     string   `xml:",innerxml"`
 }
 
 // xlsxRPr (Run Properties) specifies a set of run properties which shall be
@@ -86,13 +79,13 @@ type xlsxRPr struct {
 	RFont     *attrValString `xml:"rFont"`
 	Charset   *attrValInt    `xml:"charset"`
 	Family    *attrValInt    `xml:"family"`
-	B         *string        `xml:"b"`
-	I         *string        `xml:"i"`
-	Strike    *string        `xml:"strike"`
-	Outline   *string        `xml:"outline"`
-	Shadow    *string        `xml:"shadow"`
-	Condense  *string        `xml:"condense"`
-	Extend    *string        `xml:"extend"`
+	B         string         `xml:"b,omitempty"`
+	I         string         `xml:"i,omitempty"`
+	Strike    string         `xml:"strike,omitempty"`
+	Outline   string         `xml:"outline,omitempty"`
+	Shadow    string         `xml:"shadow,omitempty"`
+	Condense  string         `xml:"condense,omitempty"`
+	Extend    string         `xml:"extend,omitempty"`
 	Color     *xlsxColor     `xml:"color"`
 	Sz        *attrValFloat  `xml:"sz"`
 	U         *attrValString `xml:"u"`

+ 46 - 41
xmlStyles.go

@@ -1,19 +1,19 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
+// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
 // this source code is governed by a BSD-style license that can be found in
 // the LICENSE file.
 //
 // Package excelize providing a set of functions that allow you to write to
-// and read from XLSX / XLSM / XLTM files. Supports reading and writing
-// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
-// complex components by high compatibility, and provided streaming API for
-// generating or reading data from a worksheet with huge amounts of data. This
-// library needs Go version 1.15 or later.
+// and read from XLSX files. Support reads and writes XLSX file generated by
+// Microsoft Excel™ 2007 and later. Support save file without losing original
+// charts of XLSX. This library needs Go version 1.10 or later.
 
 package excelize
 
 import "encoding/xml"
 
-// xlsxStyleSheet is the root element of the Styles part.
+// xlsxStyleSheet directly maps the stylesheet element in the namespace
+// http://schemas.openxmlformats.org/spreadsheetml/2006/main - currently I have
+// not checked it for completeness - it does as much as I need.
 type xlsxStyleSheet struct {
 	XMLName      xml.Name          `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main styleSheet"`
 	NumFmts      *xlsxNumFmts      `xml:"numFmts,omitempty"`
@@ -49,11 +49,13 @@ type xlsxAlignment struct {
 // set. The cell protection properties do not take effect unless the sheet has
 // been protected.
 type xlsxProtection struct {
-	Hidden *bool `xml:"hidden,attr"`
-	Locked *bool `xml:"locked,attr"`
+	Hidden bool `xml:"hidden,attr"`
+	Locked bool `xml:"locked,attr"`
 }
 
-// xlsxLine expresses a single set of cell border.
+// xlsxLine directly maps the line style element in the namespace
+// http://schemas.openxmlformats.org/spreadsheetml/2006/main - currently I have
+// not checked it for completeness - it does as much as I need.
 type xlsxLine struct {
 	Style string     `xml:"style,attr,omitempty"`
 	Color *xlsxColor `xml:"color,omitempty"`
@@ -83,13 +85,13 @@ type xlsxFonts struct {
 // xlsxFont directly maps the font element. This element defines the
 // properties for one of the fonts used in this workbook.
 type xlsxFont struct {
-	B        *attrValBool   `xml:"b,omitempty"`
-	I        *attrValBool   `xml:"i,omitempty"`
-	Strike   *attrValBool   `xml:"strike,omitempty"`
-	Outline  *attrValBool   `xml:"outline,omitempty"`
-	Shadow   *attrValBool   `xml:"shadow,omitempty"`
-	Condense *attrValBool   `xml:"condense,omitempty"`
-	Extend   *attrValBool   `xml:"extend,omitempty"`
+	B        *bool          `xml:"b,omitempty"`
+	I        *bool          `xml:"i,omitempty"`
+	Strike   *bool          `xml:"strike,omitempty"`
+	Outline  *bool          `xml:"outline,omitempty"`
+	Shadow   *bool          `xml:"shadow,omitempty"`
+	Condense *bool          `xml:"condense,omitempty"`
+	Extend   *bool          `xml:"extend,omitempty"`
 	U        *attrValString `xml:"u"`
 	Sz       *attrValFloat  `xml:"sz"`
 	Color    *xlsxColor     `xml:"color"`
@@ -115,14 +117,17 @@ type xlsxFill struct {
 	GradientFill *xlsxGradientFill `xml:"gradientFill,omitempty"`
 }
 
-// xlsxPatternFill is used to specify cell fill information for pattern and
-// solid color cell fills. For solid cell fills (no pattern), fgColor is used.
-// For cell fills with patterns specified, then the cell fill color is
-// specified by the bgColor element.
+// xlsxPatternFill directly maps the patternFill element in the namespace
+// http://schemas.openxmlformats.org/spreadsheetml/2006/main - currently I have
+// not checked it for completeness - it does as much as I need. This element is
+// used to specify cell fill information for pattern and solid color cell fills.
+// For solid cell fills (no pattern), fgColor is used. For cell fills with
+// patterns specified, then the cell fill color is specified by the bgColor
+// element.
 type xlsxPatternFill struct {
-	PatternType string     `xml:"patternType,attr,omitempty"`
-	FgColor     *xlsxColor `xml:"fgColor"`
-	BgColor     *xlsxColor `xml:"bgColor"`
+	PatternType string    `xml:"patternType,attr,omitempty"`
+	FgColor     xlsxColor `xml:"fgColor,omitempty"`
+	BgColor     xlsxColor `xml:"bgColor,omitempty"`
 }
 
 // xlsxGradientFill defines a gradient-style cell fill. Gradient cell fills can
@@ -181,12 +186,12 @@ type xlsxCellStyles struct {
 // workbook.
 type xlsxCellStyle struct {
 	XMLName       xml.Name `xml:"cellStyle"`
-	Name          string   `xml:"name,attr"`
-	XfID          int      `xml:"xfId,attr"`
 	BuiltInID     *int     `xml:"builtinId,attr,omitempty"`
-	ILevel        *int     `xml:"iLevel,attr,omitempty"`
-	Hidden        *bool    `xml:"hidden,attr,omitempty"`
 	CustomBuiltIn *bool    `xml:"customBuiltin,attr,omitempty"`
+	Hidden        *bool    `xml:"hidden,attr,omitempty"`
+	ILevel        *bool    `xml:"iLevel,attr,omitempty"`
+	Name          string   `xml:"name,attr"`
+	XfID          int      `xml:"xfId,attr"`
 }
 
 // xlsxCellStyleXfs directly maps the cellStyleXfs element. This element
@@ -204,19 +209,19 @@ type xlsxCellStyleXfs struct {
 // xlsxXf directly maps the xf element. A single xf element describes all of the
 // formatting for a cell.
 type xlsxXf struct {
-	NumFmtID          *int            `xml:"numFmtId,attr"`
-	FontID            *int            `xml:"fontId,attr"`
-	FillID            *int            `xml:"fillId,attr"`
-	BorderID          *int            `xml:"borderId,attr"`
+	ApplyAlignment    bool            `xml:"applyAlignment,attr"`
+	ApplyBorder       bool            `xml:"applyBorder,attr"`
+	ApplyFill         bool            `xml:"applyFill,attr"`
+	ApplyFont         bool            `xml:"applyFont,attr"`
+	ApplyNumberFormat bool            `xml:"applyNumberFormat,attr"`
+	ApplyProtection   bool            `xml:"applyProtection,attr"`
+	BorderID          int             `xml:"borderId,attr"`
+	FillID            int             `xml:"fillId,attr"`
+	FontID            int             `xml:"fontId,attr"`
+	NumFmtID          int             `xml:"numFmtId,attr"`
+	PivotButton       bool            `xml:"pivotButton,attr,omitempty"`
+	QuotePrefix       bool            `xml:"quotePrefix,attr,omitempty"`
 	XfID              *int            `xml:"xfId,attr"`
-	QuotePrefix       *bool           `xml:"quotePrefix,attr"`
-	PivotButton       *bool           `xml:"pivotButton,attr"`
-	ApplyNumberFormat *bool           `xml:"applyNumberFormat,attr"`
-	ApplyFont         *bool           `xml:"applyFont,attr"`
-	ApplyFill         *bool           `xml:"applyFill,attr"`
-	ApplyBorder       *bool           `xml:"applyBorder,attr"`
-	ApplyAlignment    *bool           `xml:"applyAlignment,attr"`
-	ApplyProtection   *bool           `xml:"applyProtection,attr"`
 	Alignment         *xlsxAlignment  `xml:"alignment"`
 	Protection        *xlsxProtection `xml:"protection"`
 }
@@ -296,7 +301,7 @@ type xlsxNumFmts struct {
 // format properties which indicate how to format and render the numeric value
 // of a cell.
 type xlsxNumFmt struct {
-	NumFmtID   int    `xml:"numFmtId,attr"`
+	NumFmtID   int    `xml:"numFmtId,attr,omitempty"`
 	FormatCode string `xml:"formatCode,attr,omitempty"`
 }
 

+ 7 - 9
xmlTable.go

@@ -1,13 +1,11 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
+// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
 // this source code is governed by a BSD-style license that can be found in
 // the LICENSE file.
 //
 // Package excelize providing a set of functions that allow you to write to
-// and read from XLSX / XLSM / XLTM files. Supports reading and writing
-// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
-// complex components by high compatibility, and provided streaming API for
-// generating or reading data from a worksheet with huge amounts of data. This
-// library needs Go version 1.15 or later.
+// and read from XLSX files. Support reads and writes XLSX file generated by
+// Microsoft Excel™ 2007 and later. Support save file without losing original
+// charts of XLSX. This library needs Go version 1.10 or later.
 
 package excelize
 
@@ -46,9 +44,9 @@ type xlsxTable struct {
 // applied column by column to a table of data in the worksheet. This collection
 // expresses AutoFilter settings.
 type xlsxAutoFilter struct {
-	XMLName      xml.Name            `xml:"autoFilter"`
-	Ref          string              `xml:"ref,attr"`
-	FilterColumn []*xlsxFilterColumn `xml:"filterColumn"`
+	XMLName      xml.Name          `xml:"autoFilter"`
+	Ref          string            `xml:"ref,attr"`
+	FilterColumn *xlsxFilterColumn `xml:"filterColumn"`
 }
 
 // xlsxFilterColumn directly maps the filterColumn element. The filterColumn

+ 7 - 9
xmlTheme.go

@@ -1,13 +1,11 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
+// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
 // this source code is governed by a BSD-style license that can be found in
 // the LICENSE file.
 //
 // Package excelize providing a set of functions that allow you to write to
-// and read from XLSX / XLSM / XLTM files. Supports reading and writing
-// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
-// complex components by high compatibility, and provided streaming API for
-// generating or reading data from a worksheet with huge amounts of data. This
-// library needs Go version 1.15 or later.
+// and read from XLSX files. Support reads and writes XLSX file generated by
+// Microsoft Excel™ 2007 and later. Support save file without losing original
+// charts of XLSX. This library needs Go version 1.10 or later.
 
 package excelize
 
@@ -123,9 +121,9 @@ type xlsxBgFillStyleLst struct {
 	BgFillStyleLst string `xml:",innerxml"`
 }
 
-// xlsxClrScheme specifies the theme color, stored in the document's Theme
-// part to which the value of this theme color shall be mapped. This mapping
-// enables multiple theme colors to be chained together.
+// xlsxClrScheme maps to children of the clrScheme element in the namespace
+// http://schemas.openxmlformats.org/drawingml/2006/main - currently I have
+// not checked it for completeness - it does as much as I need.
 type xlsxClrSchemeEl struct {
 	XMLName xml.Name
 	SysClr  *xlsxSysClr    `xml:"sysClr"`

+ 9 - 19
xmlWorkbook.go

@@ -1,13 +1,11 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
+// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
 // this source code is governed by a BSD-style license that can be found in
 // the LICENSE file.
 //
 // Package excelize providing a set of functions that allow you to write to
-// and read from XLSX / XLSM / XLTM files. Supports reading and writing
-// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
-// complex components by high compatibility, and provided streaming API for
-// generating or reading data from a worksheet with huge amounts of data. This
-// library needs Go version 1.15 or later.
+// and read from XLSX files. Support reads and writes XLSX file generated by
+// Microsoft Excel™ 2007 and later. Support save file without losing original
+// charts of XLSX. This library needs Go version 1.10 or later.
 
 package excelize
 
@@ -27,31 +25,23 @@ type xlsxRelationship struct {
 	TargetMode string `xml:",attr,omitempty"`
 }
 
-// xlsxWorkbook contains elements and attributes that encompass the data
-// content of the workbook. The workbook's child elements each have their own
-// subclause references.
+// xlsxWorkbook directly maps the workbook element from the namespace
+// http://schemas.openxmlformats.org/spreadsheetml/2006/main - currently I have
+// not checked it for completeness - it does as much as I need.
 type xlsxWorkbook struct {
 	XMLName             xml.Name                 `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main workbook"`
-	Conformance         string                   `xml:"conformance,attr,omitempty"`
 	FileVersion         *xlsxFileVersion         `xml:"fileVersion"`
-	FileSharing         *xlsxExtLst              `xml:"fileSharing"`
 	WorkbookPr          *xlsxWorkbookPr          `xml:"workbookPr"`
 	WorkbookProtection  *xlsxWorkbookProtection  `xml:"workbookProtection"`
 	BookViews           *xlsxBookViews           `xml:"bookViews"`
 	Sheets              xlsxSheets               `xml:"sheets"`
-	FunctionGroups      *xlsxExtLst              `xml:"functionGroups"`
 	ExternalReferences  *xlsxExternalReferences  `xml:"externalReferences"`
 	DefinedNames        *xlsxDefinedNames        `xml:"definedNames"`
 	CalcPr              *xlsxCalcPr              `xml:"calcPr"`
-	OleSize             *xlsxExtLst              `xml:"oleSize"`
 	CustomWorkbookViews *xlsxCustomWorkbookViews `xml:"customWorkbookViews"`
 	PivotCaches         *xlsxPivotCaches         `xml:"pivotCaches"`
-	SmartTagPr          *xlsxExtLst              `xml:"smartTagPr"`
-	SmartTagTypes       *xlsxExtLst              `xml:"smartTagTypes"`
-	WebPublishing       *xlsxExtLst              `xml:"webPublishing"`
-	FileRecoveryPr      *xlsxFileRecoveryPr      `xml:"fileRecoveryPr"`
-	WebPublishObjects   *xlsxExtLst              `xml:"webPublishObjects"`
 	ExtLst              *xlsxExtLst              `xml:"extLst"`
+	FileRecoveryPr      *xlsxFileRecoveryPr      `xml:"fileRecoveryPr"`
 }
 
 // xlsxFileRecoveryPr maps sheet recovery information. This element defines
@@ -161,7 +151,7 @@ type xlsxSheets struct {
 type xlsxSheet struct {
 	Name    string `xml:"name,attr,omitempty"`
 	SheetID int    `xml:"sheetId,attr,omitempty"`
-	ID      string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr"`
+	ID      string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"`
 	State   string `xml:"state,attr,omitempty"`
 }
 

+ 57 - 59
xmlWorksheet.go

@@ -1,25 +1,19 @@
-// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
+// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
 // this source code is governed by a BSD-style license that can be found in
 // the LICENSE file.
 //
 // Package excelize providing a set of functions that allow you to write to
-// and read from XLSX / XLSM / XLTM files. Supports reading and writing
-// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
-// complex components by high compatibility, and provided streaming API for
-// generating or reading data from a worksheet with huge amounts of data. This
-// library needs Go version 1.15 or later.
+// and read from XLSX files. Support reads and writes XLSX file generated by
+// Microsoft Excel™ 2007 and later. Support save file without losing original
+// charts of XLSX. This library needs Go version 1.10 or later.
 
 package excelize
 
-import (
-	"encoding/xml"
-	"sync"
-)
+import "encoding/xml"
 
 // xlsxWorksheet directly maps the worksheet element in the namespace
 // http://schemas.openxmlformats.org/spreadsheetml/2006/main.
 type xlsxWorksheet struct {
-	sync.Mutex
 	XMLName               xml.Name                     `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main worksheet"`
 	SheetPr               *xlsxSheetPr                 `xml:"sheetPr"`
 	Dimension             *xlsxDimension               `xml:"dimension"`
@@ -169,20 +163,25 @@ type xlsxSheetFormatPr struct {
 	OutlineLevelCol  uint8    `xml:"outlineLevelCol,attr,omitempty"`
 }
 
-// xlsxSheetViews represents worksheet views collection.
+// xlsxSheetViews directly maps the sheetViews element in the namespace
+// http://schemas.openxmlformats.org/spreadsheetml/2006/main - Worksheet views
+// collection.
 type xlsxSheetViews struct {
 	XMLName   xml.Name        `xml:"sheetViews"`
 	SheetView []xlsxSheetView `xml:"sheetView"`
 }
 
-// xlsxSheetView represents a single sheet view definition. When more than one
-// sheet view is defined in the file, it means that when opening the workbook,
-// each sheet view corresponds to a separate window within the spreadsheet
-// application, where each window is showing the particular sheet containing
-// the same workbookViewId value, the last sheetView definition is loaded, and
-// the others are discarded. When multiple windows are viewing the same sheet,
-// multiple sheetView elements (with corresponding workbookView entries) are
-// saved.
+// xlsxSheetView directly maps the sheetView element in the namespace
+// http://schemas.openxmlformats.org/spreadsheetml/2006/main - currently I have
+// not checked it for completeness - it does as much as I need. A single sheet
+// view definition. When more than one sheet view is defined in the file, it
+// means that when opening the workbook, each sheet view corresponds to a
+// separate window within the spreadsheet application, where each window is
+// showing the particular sheet containing the same workbookViewId value, the
+// last sheetView definition is loaded, and the others are discarded. When
+// multiple windows are viewing the same sheet, multiple sheetView elements
+// (with corresponding workbookView entries) are saved.
+// See https://docs.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.sheetview
 type xlsxSheetView struct {
 	WindowProtection         bool             `xml:"windowProtection,attr,omitempty"`
 	ShowFormulas             bool             `xml:"showFormulas,attr,omitempty"`
@@ -234,42 +233,41 @@ type xlsxSheetPr struct {
 	SyncVertical                      bool             `xml:"syncVertical,attr,omitempty"`
 	SyncRef                           string           `xml:"syncRef,attr,omitempty"`
 	TransitionEvaluation              bool             `xml:"transitionEvaluation,attr,omitempty"`
-	TransitionEntry                   bool             `xml:"transitionEntry,attr,omitempty"`
 	Published                         *bool            `xml:"published,attr"`
 	CodeName                          string           `xml:"codeName,attr,omitempty"`
 	FilterMode                        bool             `xml:"filterMode,attr,omitempty"`
 	EnableFormatConditionsCalculation *bool            `xml:"enableFormatConditionsCalculation,attr"`
+	TransitionEntry                   bool             `xml:"transitionEntry,attr,omitempty"`
 	TabColor                          *xlsxTabColor    `xml:"tabColor,omitempty"`
 	OutlinePr                         *xlsxOutlinePr   `xml:"outlinePr,omitempty"`
 	PageSetUpPr                       *xlsxPageSetUpPr `xml:"pageSetUpPr,omitempty"`
 }
 
-// xlsxOutlinePr maps to the outlinePr element. SummaryBelow allows you to
-// adjust the direction of grouper controls.
+// xlsxOutlinePr maps to the outlinePr element
+// SummaryBelow allows you to adjust the direction of grouper controls
 type xlsxOutlinePr struct {
-	ApplyStyles        *bool `xml:"applyStyles,attr"`
-	SummaryBelow       bool  `xml:"summaryBelow,attr"`
-	SummaryRight       bool  `xml:"summaryRight,attr"`
-	ShowOutlineSymbols bool  `xml:"showOutlineSymbols,attr"`
+	SummaryBelow bool `xml:"summaryBelow,attr"`
 }
 
-// xlsxPageSetUpPr expresses page setup properties of the worksheet.
+// xlsxPageSetUpPr directly maps the pageSetupPr element in the namespace
+// http://schemas.openxmlformats.org/spreadsheetml/2006/main - Page setup
+// properties of the worksheet.
 type xlsxPageSetUpPr struct {
 	AutoPageBreaks bool `xml:"autoPageBreaks,attr,omitempty"`
-	FitToPage      bool `xml:"fitToPage,attr,omitempty"`
+	FitToPage      bool `xml:"fitToPage,attr,omitempty"` // Flag indicating whether the Fit to Page print option is enabled.
 }
 
-// xlsxTabColor represents background color of the sheet tab.
+// xlsxTabColor directly maps the tabColor element in the namespace currently I
+// have not checked it for completeness - it does as much as I need.
 type xlsxTabColor struct {
-	Auto    bool    `xml:"auto,attr,omitempty"`
-	Indexed int     `xml:"indexed,attr,omitempty"`
-	RGB     string  `xml:"rgb,attr,omitempty"`
-	Theme   int     `xml:"theme,attr,omitempty"`
-	Tint    float64 `xml:"tint,attr,omitempty"`
+	RGB   string  `xml:"rgb,attr,omitempty"`
+	Theme int     `xml:"theme,attr,omitempty"`
+	Tint  float64 `xml:"tint,attr,omitempty"`
 }
 
-// xlsxCols defines column width and column formatting for one or more columns
-// of the worksheet.
+// xlsxCols directly maps the cols element in the namespace
+// http://schemas.openxmlformats.org/spreadsheetml/2006/main - currently I have
+// not checked it for completeness - it does as much as I need.
 type xlsxCols struct {
 	XMLName xml.Name  `xml:"cols"`
 	Col     []xlsxCol `xml:"col"`
@@ -293,18 +291,18 @@ type xlsxCol struct {
 // xlsxDimension directly maps the dimension element in the namespace
 // http://schemas.openxmlformats.org/spreadsheetml/2006/main - This element
 // specifies the used range of the worksheet. It specifies the row and column
-// bounds of used cells in the worksheet. This is optional and is not
-// required. Used cells include cells with formulas, text content, and cell
-// formatting. When an entire column is formatted, only the first cell in that
-// column is considered used.
+// bounds of used cells in the worksheet. This is optional and is not required.
+// Used cells include cells with formulas, text content, and cell formatting.
+// When an entire column is formatted, only the first cell in that column is
+// considered used.
 type xlsxDimension struct {
 	XMLName xml.Name `xml:"dimension"`
 	Ref     string   `xml:"ref,attr"`
 }
 
-// xlsxSheetData collection represents the cell table itself. This collection
-// expresses information about each cell, grouped together by rows in the
-// worksheet.
+// xlsxSheetData directly maps the sheetData element in the namespace
+// http://schemas.openxmlformats.org/spreadsheetml/2006/main - currently I have
+// not checked it for completeness - it does as much as I need.
 type xlsxSheetData struct {
 	XMLName xml.Name  `xml:"sheetData"`
 	Row     []xlsxRow `xml:"row"`
@@ -314,19 +312,19 @@ type xlsxSheetData struct {
 // about an entire row of a worksheet, and contains all cell definitions for a
 // particular row in the worksheet.
 type xlsxRow struct {
-	C            []xlsxC `xml:"c"`
-	R            int     `xml:"r,attr,omitempty"`
-	Spans        string  `xml:"spans,attr,omitempty"`
-	S            int     `xml:"s,attr,omitempty"`
+	Collapsed    bool    `xml:"collapsed,attr,omitempty"`
 	CustomFormat bool    `xml:"customFormat,attr,omitempty"`
-	Ht           float64 `xml:"ht,attr,omitempty"`
-	Hidden       bool    `xml:"hidden,attr,omitempty"`
 	CustomHeight bool    `xml:"customHeight,attr,omitempty"`
+	Hidden       bool    `xml:"hidden,attr,omitempty"`
+	Ht           float64 `xml:"ht,attr,omitempty"`
 	OutlineLevel uint8   `xml:"outlineLevel,attr,omitempty"`
-	Collapsed    bool    `xml:"collapsed,attr,omitempty"`
-	ThickTop     bool    `xml:"thickTop,attr,omitempty"`
-	ThickBot     bool    `xml:"thickBot,attr,omitempty"`
 	Ph           bool    `xml:"ph,attr,omitempty"`
+	R            int     `xml:"r,attr,omitempty"`
+	S            int     `xml:"s,attr,omitempty"`
+	Spans        string  `xml:"spans,attr,omitempty"`
+	ThickBot     bool    `xml:"thickBot,attr,omitempty"`
+	ThickTop     bool    `xml:"thickTop,attr,omitempty"`
+	C            []xlsxC `xml:"c"`
 }
 
 // xlsxSortState directly maps the sortState element. This collection
@@ -440,9 +438,9 @@ type DataValidation struct {
 	Formula2         string  `xml:",innerxml"`
 }
 
-// xlsxC collection represents a cell in the worksheet. Information about the
-// cell's location (reference), value, data type, formatting, and formula is
-// expressed here.
+// xlsxC directly maps the c element in the namespace
+// http://schemas.openxmlformats.org/spreadsheetml/2006/main - currently I have
+// not checked it for completeness - it does as much as I need.
 //
 // This simple type is restricted to the values listed in the following table:
 //
@@ -472,8 +470,9 @@ func (c *xlsxC) hasValue() bool {
 	return c.S != 0 || c.V != "" || c.F != nil || c.T != ""
 }
 
-// xlsxF represents a formula for the cell. The formula expression is
-// contained in the character node of this element.
+// xlsxF directly maps the f element in the namespace
+// http://schemas.openxmlformats.org/spreadsheetml/2006/main - currently I have
+// not checked it for completeness - it does as much as I need.
 type xlsxF struct {
 	Content string `xml:",chardata"`
 	T       string `xml:"t,attr,omitempty"`   // Formula type
@@ -604,7 +603,6 @@ type xlsxHyperlink struct {
 	Ref      string `xml:"ref,attr"`
 	Location string `xml:"location,attr,omitempty"`
 	Display  string `xml:"display,attr,omitempty"`
-	Tooltip  string `xml:"tooltip,attr,omitempty"`
 	RID      string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"`
 }