173 changes: 98 additions & 75 deletions adjust_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,172 +48,192 @@ func TestAdjustMergeCells(t *testing.T) {

// testing adjustMergeCells
var cases []struct {
lable string
ws *xlsxWorksheet
dir adjustDirection
num int
offset int
expect string
label string
ws *xlsxWorksheet
dir adjustDirection
num int
offset int
expect string
expectRect []int
}

// testing insert
cases = []struct {
lable string
ws *xlsxWorksheet
dir adjustDirection
num int
offset int
expect string
label string
ws *xlsxWorksheet
dir adjustDirection
num int
offset int
expect string
expectRect []int
}{
{
lable: "insert row on ref",
label: "insert row on ref",
ws: &xlsxWorksheet{
MergeCells: &xlsxMergeCells{
Cells: []*xlsxMergeCell{
{
Ref: "A2:B3",
Ref: "A2:B3",
rect: []int{1, 2, 2, 3},
},
},
},
},
dir: rows,
num: 2,
offset: 1,
expect: "A3:B4",
dir: rows,
num: 2,
offset: 1,
expect: "A3:B4",
expectRect: []int{1, 3, 2, 4},
},
{
lable: "insert row on bottom of ref",
label: "insert row on bottom of ref",
ws: &xlsxWorksheet{
MergeCells: &xlsxMergeCells{
Cells: []*xlsxMergeCell{
{
Ref: "A2:B3",
Ref: "A2:B3",
rect: []int{1, 2, 2, 3},
},
},
},
},
dir: rows,
num: 3,
offset: 1,
expect: "A2:B4",
dir: rows,
num: 3,
offset: 1,
expect: "A2:B4",
expectRect: []int{1, 2, 2, 4},
},
{
lable: "insert column on the left",
label: "insert column on the left",
ws: &xlsxWorksheet{
MergeCells: &xlsxMergeCells{
Cells: []*xlsxMergeCell{
{
Ref: "A2:B3",
Ref: "A2:B3",
rect: []int{1, 2, 2, 3},
},
},
},
},
dir: columns,
num: 1,
offset: 1,
expect: "B2:C3",
dir: columns,
num: 1,
offset: 1,
expect: "B2:C3",
expectRect: []int{2, 2, 3, 3},
},
}
for _, c := range cases {
assert.NoError(t, f.adjustMergeCells(c.ws, c.dir, c.num, 1))
assert.Equal(t, c.expect, c.ws.MergeCells.Cells[0].Ref, c.lable)
assert.Equal(t, c.expect, c.ws.MergeCells.Cells[0].Ref, c.label)
assert.Equal(t, c.expectRect, c.ws.MergeCells.Cells[0].rect, c.label)
}

// testing delete
cases = []struct {
lable string
ws *xlsxWorksheet
dir adjustDirection
num int
offset int
expect string
label string
ws *xlsxWorksheet
dir adjustDirection
num int
offset int
expect string
expectRect []int
}{
{
lable: "delete row on top of ref",
label: "delete row on top of ref",
ws: &xlsxWorksheet{
MergeCells: &xlsxMergeCells{
Cells: []*xlsxMergeCell{
{
Ref: "A2:B3",
Ref: "A2:B3",
rect: []int{1, 2, 2, 3},
},
},
},
},
dir: rows,
num: 2,
offset: -1,
expect: "A2:B2",
dir: rows,
num: 2,
offset: -1,
expect: "A2:B2",
expectRect: []int{1, 2, 2, 2},
},
{
lable: "delete row on bottom of ref",
label: "delete row on bottom of ref",
ws: &xlsxWorksheet{
MergeCells: &xlsxMergeCells{
Cells: []*xlsxMergeCell{
{
Ref: "A2:B3",
Ref: "A2:B3",
rect: []int{1, 2, 2, 3},
},
},
},
},
dir: rows,
num: 3,
offset: -1,
expect: "A2:B2",
dir: rows,
num: 3,
offset: -1,
expect: "A2:B2",
expectRect: []int{1, 2, 2, 2},
},
{
lable: "delete column on the ref left",
label: "delete column on the ref left",
ws: &xlsxWorksheet{
MergeCells: &xlsxMergeCells{
Cells: []*xlsxMergeCell{
{
Ref: "A2:B3",
Ref: "A2:B3",
rect: []int{1, 2, 2, 3},
},
},
},
},
dir: columns,
num: 1,
offset: -1,
expect: "A2:A3",
dir: columns,
num: 1,
offset: -1,
expect: "A2:A3",
expectRect: []int{1, 2, 1, 3},
},
{
lable: "delete column on the ref right",
label: "delete column on the ref right",
ws: &xlsxWorksheet{
MergeCells: &xlsxMergeCells{
Cells: []*xlsxMergeCell{
{
Ref: "A2:B3",
Ref: "A2:B3",
rect: []int{1, 2, 2, 3},
},
},
},
},
dir: columns,
num: 2,
offset: -1,
expect: "A2:A3",
dir: columns,
num: 2,
offset: -1,
expect: "A2:A3",
expectRect: []int{1, 2, 1, 3},
},
}
for _, c := range cases {
assert.NoError(t, f.adjustMergeCells(c.ws, c.dir, c.num, -1))
assert.Equal(t, c.expect, c.ws.MergeCells.Cells[0].Ref, c.lable)
assert.Equal(t, c.expect, c.ws.MergeCells.Cells[0].Ref, c.label)
}

// testing delete one row/column
cases = []struct {
lable string
ws *xlsxWorksheet
dir adjustDirection
num int
offset int
expect string
label string
ws *xlsxWorksheet
dir adjustDirection
num int
offset int
expect string
expectRect []int
}{
{
lable: "delete one row ref",
label: "delete one row ref",
ws: &xlsxWorksheet{
MergeCells: &xlsxMergeCells{
Cells: []*xlsxMergeCell{
{
Ref: "A1:B1",
Ref: "A1:B1",
rect: []int{1, 1, 2, 1},
},
},
},
Expand All @@ -223,12 +243,13 @@ func TestAdjustMergeCells(t *testing.T) {
offset: -1,
},
{
lable: "delete one column ref",
label: "delete one column ref",
ws: &xlsxWorksheet{
MergeCells: &xlsxMergeCells{
Cells: []*xlsxMergeCell{
{
Ref: "A1:A2",
Ref: "A1:A2",
rect: []int{1, 1, 1, 2},
},
},
},
Expand All @@ -240,7 +261,7 @@ func TestAdjustMergeCells(t *testing.T) {
}
for _, c := range cases {
assert.NoError(t, f.adjustMergeCells(c.ws, c.dir, c.num, -1))
assert.Equal(t, 0, len(c.ws.MergeCells.Cells), c.lable)
assert.Equal(t, 0, len(c.ws.MergeCells.Cells), c.label)
}

f = NewFile()
Expand Down Expand Up @@ -277,9 +298,11 @@ func TestAdjustHelper(t *testing.T) {
f := NewFile()
f.NewSheet("Sheet2")
f.Sheet.Store("xl/worksheets/sheet1.xml", &xlsxWorksheet{
MergeCells: &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:B1"}}}})
MergeCells: &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:B1"}}},
})
f.Sheet.Store("xl/worksheets/sheet2.xml", &xlsxWorksheet{
AutoFilter: &xlsxAutoFilter{Ref: "A1:B"}})
AutoFilter: &xlsxAutoFilter{Ref: "A1:B"},
})
// testing adjustHelper with illegal cell coordinates.
assert.EqualError(t, f.adjustHelper("Sheet1", rows, 0, 0), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
assert.EqualError(t, f.adjustHelper("Sheet2", rows, 0, 0), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")).Error())
Expand Down
4,735 changes: 4,006 additions & 729 deletions calc.go

Large diffs are not rendered by default.

1,385 changes: 1,224 additions & 161 deletions calc_test.go

Large diffs are not rendered by default.

20 changes: 10 additions & 10 deletions calcchain.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2022 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 providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excelâ„¢ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.15 or later.

package excelize

Expand All @@ -25,7 +25,7 @@ func (f *File) calcChainReader() *xlsxCalcChain {

if f.CalcChain == nil {
f.CalcChain = new(xlsxCalcChain)
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(dafaultXMLPathCalcChain)))).
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathCalcChain)))).
Decode(f.CalcChain); err != nil && err != io.EOF {
log.Printf("xml decode error: %s", err)
}
Expand All @@ -39,7 +39,7 @@ func (f *File) calcChainReader() *xlsxCalcChain {
func (f *File) calcChainWriter() {
if f.CalcChain != nil && f.CalcChain.C != nil {
output, _ := xml.Marshal(f.CalcChain)
f.saveFileList(dafaultXMLPathCalcChain, output)
f.saveFileList(defaultXMLPathCalcChain, output)
}
}

Expand All @@ -54,7 +54,7 @@ func (f *File) deleteCalcChain(index int, axis string) {
}
if len(calc.C) == 0 {
f.CalcChain = nil
f.Pkg.Delete(dafaultXMLPathCalcChain)
f.Pkg.Delete(defaultXMLPathCalcChain)
content := f.contentTypesReader()
content.Lock()
defer content.Unlock()
Expand Down
2 changes: 1 addition & 1 deletion calcchain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import "testing"
func TestCalcChainReader(t *testing.T) {
f := NewFile()
f.CalcChain = nil
f.Pkg.Store(dafaultXMLPathCalcChain, MacintoshCyrillicCharset)
f.Pkg.Store(defaultXMLPathCalcChain, MacintoshCyrillicCharset)
f.calcChainReader()
}

Expand Down
181 changes: 96 additions & 85 deletions cell.go

Large diffs are not rendered by default.

89 changes: 78 additions & 11 deletions cell_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package excelize

import (
"fmt"
"os"
"path/filepath"
"reflect"
"strconv"
Expand Down Expand Up @@ -29,11 +30,13 @@ func TestConcurrency(t *testing.T) {
_, err := f.GetCellValue("Sheet1", fmt.Sprintf("A%d", val))
assert.NoError(t, err)
// Concurrency set rows
assert.NoError(t, f.SetSheetRow("Sheet1", "B6", &[]interface{}{" Hello",
assert.NoError(t, f.SetSheetRow("Sheet1", "B6", &[]interface{}{
" Hello",
[]byte("World"), 42, int8(1<<8/2 - 1), int16(1<<16/2 - 1), int32(1<<32/2 - 1),
int64(1<<32/2 - 1), float32(42.65418), float64(-42.65418), float32(42), float64(42),
int64(1<<32/2 - 1), float32(42.65418), -42.65418, float32(42), float64(42),
uint(1<<32 - 1), uint8(1<<8 - 1), uint16(1<<16 - 1), uint32(1<<32 - 1),
uint64(1<<32 - 1), true, complex64(5 + 10i)}))
uint64(1<<32 - 1), true, complex64(5 + 10i),
}))
// Concurrency create style
style, err := f.NewStyle(`{"font":{"color":"#1265BE","underline":"single"}}`)
assert.NoError(t, err)
Expand Down Expand Up @@ -127,7 +130,7 @@ func TestSetCellFloat(t *testing.T) {
assert.Equal(t, "123", val, "A1 should be 123")
val, err = f.GetCellValue(sheet, "A2")
assert.NoError(t, err)
assert.Equal(t, "123.0", val, "A2 should be 123.0")
assert.Equal(t, "123", val, "A2 should be 123")
})

t.Run("with a decimal and precision limit", func(t *testing.T) {
Expand Down Expand Up @@ -285,12 +288,14 @@ func TestGetCellValue(t *testing.T) {
<c r="S1"><v>275.39999999999998</v></c>
<c r="T1"><v>68.900000000000006</v></c>
<c r="U1"><v>8.8880000000000001E-2</v></c>
<c r="V1"><v>4.0000000000000003E-5</v></c>
<c r="V1"><v>4.0000000000000003e-5</v></c>
<c r="W1"><v>2422.3000000000002</v></c>
<c r="X1"><v>1101.5999999999999</v></c>
<c r="Y1"><v>275.39999999999998</v></c>
<c r="Z1"><v>68.900000000000006</v></c>
<c r="AA1"><v>1.1000000000000001</v></c>
<c r="AA2"><v>1234567890123_4</v></c>
<c r="AA3"><v>123456789_0123_4</v></c>
</row>`)))
f.checked = nil
rows, err = f.GetRows("Sheet1")
Expand Down Expand Up @@ -322,6 +327,8 @@ func TestGetCellValue(t *testing.T) {
"275.4",
"68.9",
"1.1",
"1234567890123_4",
"123456789_0123_4",
}}, rows)
assert.NoError(t, err)
}
Expand All @@ -339,6 +346,14 @@ func TestGetCellType(t *testing.T) {
assert.EqualError(t, err, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
}

func TestGetValueFrom(t *testing.T) {
f := NewFile()
c := xlsxC{T: "s"}
value, err := c.getValueFrom(f, f.sharedStringsReader(), false)
assert.NoError(t, err)
assert.Equal(t, "", value)
}

func TestGetCellFormula(t *testing.T) {
// Test get cell formula on not exist worksheet.
f := NewFile()
Expand Down Expand Up @@ -375,7 +390,7 @@ func TestGetCellFormula(t *testing.T) {

func ExampleFile_SetCellFloat() {
f := NewFile()
var x = 3.14159265
x := 3.14159265
if err := f.SetCellFloat("Sheet1", "A1", x, 2, 64); err != nil {
fmt.Println(err)
}
Expand Down Expand Up @@ -525,6 +540,7 @@ func TestGetCellRichText(t *testing.T) {
_, err = f.GetCellRichText("Sheet1", "A")
assert.EqualError(t, err, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
}

func TestSetCellRichText(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetRowHeight("Sheet1", 1, 35))
Expand Down Expand Up @@ -653,16 +669,67 @@ func TestFormattedValue2(t *testing.T) {
func TestSharedStringsError(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"), Options{UnzipXMLSizeLimit: 128})
assert.NoError(t, err)
f.tempFiles.Store(dafaultXMLPathSharedStrings, "")
assert.Equal(t, "1", f.getFromStringItemMap(1))

tempFile, ok := f.tempFiles.Load(defaultXMLPathSharedStrings)
assert.True(t, ok)
f.tempFiles.Store(defaultXMLPathSharedStrings, "")
assert.Equal(t, "1", f.getFromStringItem(1))
// Cleanup undelete temporary files
assert.NoError(t, os.Remove(tempFile.(string)))
// Test reload the file error on set cell cell and rich text. The error message was different between macOS and Windows.
err = f.SetCellValue("Sheet1", "A19", "A19")
assert.Error(t, err)

f.tempFiles.Store(dafaultXMLPathSharedStrings, "")
f.tempFiles.Store(defaultXMLPathSharedStrings, "")
err = f.SetCellRichText("Sheet1", "A19", []RichTextRun{})
assert.Error(t, err)

assert.NoError(t, f.Close())

f, err = OpenFile(filepath.Join("test", "Book1.xlsx"), Options{UnzipXMLSizeLimit: 128})
assert.NoError(t, err)
rows, err := f.Rows("Sheet1")
assert.NoError(t, err)
const maxUint16 = 1<<16 - 1
currentRow := 0
for rows.Next() {
currentRow++
if currentRow == 19 {
_, err := rows.Columns()
assert.NoError(t, err)
// Test get cell value from string item with invalid offset
f.sharedStringItem[1] = []uint{maxUint16 - 1, maxUint16}
assert.Equal(t, "1", f.getFromStringItem(1))
break
}
}
assert.NoError(t, rows.Close())
// Test shared string item temporary files has been closed before close the workbook
assert.NoError(t, f.sharedStringTemp.Close())
assert.Error(t, f.Close())
// Cleanup undelete temporary files
f.tempFiles.Range(func(k, v interface{}) bool {
return assert.NoError(t, os.Remove(v.(string)))
})

f, err = OpenFile(filepath.Join("test", "Book1.xlsx"), Options{UnzipXMLSizeLimit: 128})
assert.NoError(t, err)
rows, err = f.Rows("Sheet1")
assert.NoError(t, err)
currentRow = 0
for rows.Next() {
currentRow++
if currentRow == 19 {
_, err := rows.Columns()
assert.NoError(t, err)
break
}
}
assert.NoError(t, rows.Close())
assert.NoError(t, f.sharedStringTemp.Close())
// Test shared string item temporary files has been closed before set the cell value
assert.Error(t, f.SetCellValue("Sheet1", "A1", "A1"))
assert.Error(t, f.Close())
// Cleanup undelete temporary files
f.tempFiles.Range(func(k, v interface{}) bool {
return assert.NoError(t, os.Remove(v.(string)))
})
}
20 changes: 10 additions & 10 deletions chart.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2022 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 providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excelâ„¢ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.15 or later.

package excelize

Expand Down Expand Up @@ -969,7 +969,7 @@ func (f *File) AddChartSheet(sheet, format string, combo ...string) error {
// getFormatChart provides a function to check format set of the chart and
// create chart format.
func (f *File) getFormatChart(format string, combo []string) (*formatChart, []*formatChart, error) {
comboCharts := []*formatChart{}
var comboCharts []*formatChart
formatSet, err := parseFormatChartSet(format)
if err != nil {
return formatSet, comboCharts, err
Expand All @@ -980,12 +980,12 @@ func (f *File) getFormatChart(format string, combo []string) (*formatChart, []*f
return formatSet, comboCharts, err
}
if _, ok := chartValAxNumFmtFormatCode[comboChart.Type]; !ok {
return formatSet, comboCharts, newUnsupportChartType(comboChart.Type)
return formatSet, comboCharts, newUnsupportedChartType(comboChart.Type)
}
comboCharts = append(comboCharts, comboChart)
}
if _, ok := chartValAxNumFmtFormatCode[formatSet.Type]; !ok {
return formatSet, comboCharts, newUnsupportChartType(formatSet.Type)
return formatSet, comboCharts, newUnsupportedChartType(formatSet.Type)
}
return formatSet, comboCharts, err
}
Expand Down
4 changes: 2 additions & 2 deletions chart_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ func TestAddChart(t *testing.T) {
assert.NoError(t, f.AddChart("Sheet1", "P45", `{"type":"col3D","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Column Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet2", "P1", `{"type":"radar","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"top_right","show_legend_key":false},"title":{"name":"Radar Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"span"}`))
assert.NoError(t, f.AddChart("Sheet2", "X1", `{"type":"scatter","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"bottom","show_legend_key":false},"title":{"name":"Scatter Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet2", "P16", `{"type":"doughnut","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$A$30:$D$37","values":"Sheet1!$B$30:$B$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"right","show_legend_key":false},"title":{"name":"Doughnut Chart"},"plotarea":{"show_bubble_size":false,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":false,"show_val":false},"show_blanks_as":"zero"}`))
assert.NoError(t, f.AddChart("Sheet2", "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","hole_size":30}`))
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", "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"}`))
Expand Down Expand Up @@ -353,7 +353,7 @@ func TestChartWithLogarithmicBase(t *testing.T) {
}
assert.True(t, ok, "Can't open the %s", chartPath)

err = xml.Unmarshal([]byte(xmlCharts[i]), &chartSpaces[i])
err = xml.Unmarshal(xmlCharts[i], &chartSpaces[i])
if !assert.NoError(t, err) {
t.FailNow()
}
Expand Down
56 changes: 21 additions & 35 deletions col.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2022 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 providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excelâ„¢ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.15 or later.

package excelize

Expand Down Expand Up @@ -40,18 +40,7 @@ type Cols struct {
sheetXML []byte
}

// CurrentCol returns the column number that represents the current column.
func (cols *Cols) CurrentCol() int {
return cols.curCol
}

// TotalCols returns the total columns count in the worksheet.
func (cols *Cols) TotalCols() int {
return cols.totalCols
}

// GetCols return all the columns in a sheet by given worksheet name (case
// sensitive). For example:
// GetCols return all the columns in a sheet by given worksheet name (case-sensitive). For example:
//
// cols, err := f.GetCols("Sheet1")
// if err != nil {
Expand Down Expand Up @@ -250,20 +239,18 @@ func (f *File) Cols(sheet string) (*Cols, error) {
// visible, err := f.GetColVisible("Sheet1", "D")
//
func (f *File) GetColVisible(sheet, col string) (bool, error) {
visible := true
colNum, err := ColumnNameToNumber(col)
if err != nil {
return visible, err
return true, err
}

ws, err := f.workSheetReader(sheet)
if err != nil {
return false, err
}
if ws.Cols == nil {
return visible, err
return true, err
}

visible := true
for c := range ws.Cols.Col {
colData := &ws.Cols.Col[c]
if colData.Min <= colNum && colNum <= colData.Max {
Expand Down Expand Up @@ -465,12 +452,12 @@ func (f *File) SetColStyle(sheet, columns string, styleID int) error {
// f := excelize.NewFile()
// err := f.SetColWidth("Sheet1", "A", "H", 20)
//
func (f *File) SetColWidth(sheet, startcol, endcol string, width float64) error {
min, err := ColumnNameToNumber(startcol)
func (f *File) SetColWidth(sheet, startCol, endCol string, width float64) error {
min, err := ColumnNameToNumber(startCol)
if err != nil {
return err
}
max, err := ColumnNameToNumber(endcol)
max, err := ColumnNameToNumber(endCol)
if err != nil {
return err
}
Expand Down Expand Up @@ -512,7 +499,7 @@ func (f *File) SetColWidth(sheet, startcol, endcol string, width float64) error
// flatCols provides a method for the column's operation functions to flatten
// and check the worksheet columns.
func flatCols(col xlsxCol, cols []xlsxCol, replacer func(fc, c xlsxCol) xlsxCol) []xlsxCol {
fc := []xlsxCol{}
var fc []xlsxCol
for i := col.Min; i <= col.Max; i++ {
c := deepcopy.Copy(col).(xlsxCol)
c.Min, c.Max = i, i
Expand Down Expand Up @@ -557,7 +544,7 @@ func flatCols(col xlsxCol, cols []xlsxCol, replacer func(fc, c xlsxCol) xlsxCol)
// | | | (x2,y2)|
// +-----+------------+------------+
//
// Example of an object that covers some of the area from cell A1 to B2.
// Example of an object that covers some area from cell A1 to B2.
//
// Based on the width and height of the object we need to calculate 8 vars:
//
Expand Down Expand Up @@ -602,9 +589,8 @@ func (f *File) positionObjectPixels(sheet string, col, row, x1, y1, width, heigh
row++
}

// Initialise end cell to the same as the start cell.
colEnd := col
rowEnd := row
// Initialized end cell to the same as the start cell.
colEnd, rowEnd := col, row

width += x1
height += y1
Expand Down Expand Up @@ -642,7 +628,7 @@ func (f *File) getColWidth(sheet string, col int) int {
return int(convertColWidthToPixels(width))
}
}
// Optimisation for when the column widths haven't changed.
// Optimization for when the column widths haven't changed.
return int(defaultColWidthPixels)
}

Expand All @@ -668,7 +654,7 @@ func (f *File) GetColWidth(sheet, col string) (float64, error) {
return width, err
}
}
// Optimisation for when the column widths haven't changed.
// Optimization for when the column widths haven't changed.
return defaultColWidth, err
}

Expand Down Expand Up @@ -717,7 +703,7 @@ func (f *File) RemoveCol(sheet, col string) error {
return f.adjustHelper(sheet, columns, num, -1)
}

// convertColWidthToPixels provieds function to convert the width of a cell
// convertColWidthToPixels provides function to convert the width of a cell
// from user's units to pixels. Excel rounds the column width to the nearest
// pixel. If the width hasn't been set by the user we use the default value.
// If the column is hidden it has a value of zero.
Expand Down
29 changes: 18 additions & 11 deletions col_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,6 @@ func TestColumnsIterator(t *testing.T) {

for cols.Next() {
colCount++
assert.Equal(t, colCount, cols.CurrentCol())
assert.Equal(t, expectedNumCol, cols.TotalCols())
require.True(t, colCount <= expectedNumCol, "colCount is greater than expected")
}
assert.Equal(t, expectedNumCol, colCount)
Expand All @@ -85,8 +83,6 @@ func TestColumnsIterator(t *testing.T) {

for cols.Next() {
colCount++
assert.Equal(t, colCount, cols.CurrentCol())
assert.Equal(t, expectedNumCol, cols.TotalCols())
require.True(t, colCount <= 4, "colCount is greater than expected")
}
assert.Equal(t, expectedNumCol, colCount)
Expand Down Expand Up @@ -131,6 +127,11 @@ func TestGetColsError(t *testing.T) {
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, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())

f.Pkg.Store("xl/worksheets/sheet1.xml", nil)
f.Sheet.Store("xl/worksheets/sheet1.xml", nil)
_, err = f.Cols("Sheet1")
assert.NoError(t, err)
}

func TestColsRows(t *testing.T) {
Expand Down Expand Up @@ -288,18 +289,24 @@ func TestOutlineLevel(t *testing.T) {
func TestSetColStyle(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetCellValue("Sheet1", "B2", "Hello"))
style, err := f.NewStyle(`{"fill":{"type":"pattern","color":["#94d3a2"],"pattern":1}}`)
styleID, err := f.NewStyle(`{"fill":{"type":"pattern","color":["#94d3a2"],"pattern":1}}`)
assert.NoError(t, err)
// Test set column style on not exists worksheet.
assert.EqualError(t, f.SetColStyle("SheetN", "E", style), "sheet SheetN is not exist")
assert.EqualError(t, f.SetColStyle("SheetN", "E", styleID), "sheet SheetN is not exist")
// Test set column style with illegal cell coordinates.
assert.EqualError(t, f.SetColStyle("Sheet1", "*", style), newInvalidColumnNameError("*").Error())
assert.EqualError(t, f.SetColStyle("Sheet1", "A:*", style), newInvalidColumnNameError("*").Error())
assert.EqualError(t, f.SetColStyle("Sheet1", "*", styleID), newInvalidColumnNameError("*").Error())
assert.EqualError(t, f.SetColStyle("Sheet1", "A:*", styleID), newInvalidColumnNameError("*").Error())

assert.NoError(t, f.SetColStyle("Sheet1", "B", style))
assert.NoError(t, f.SetColStyle("Sheet1", "B", styleID))
// Test set column style with already exists column with style.
assert.NoError(t, f.SetColStyle("Sheet1", "B", style))
assert.NoError(t, f.SetColStyle("Sheet1", "D:C", style))
assert.NoError(t, f.SetColStyle("Sheet1", "B", styleID))
assert.NoError(t, f.SetColStyle("Sheet1", "D:C", styleID))
ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).SheetData.Row[1].C[2].S = 0
cellStyleID, err := f.GetCellStyle("Sheet1", "C2")
assert.NoError(t, err)
assert.Equal(t, styleID, cellStyleID)
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetColStyle.xlsx")))
}

Expand Down
20 changes: 10 additions & 10 deletions comment.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2022 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 providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excelâ„¢ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.15 or later.

package excelize

Expand Down Expand Up @@ -47,7 +47,7 @@ func (f *File) GetComments() (comments map[string][]Comment) {
target = "xl" + strings.TrimPrefix(target, "..")
}
if d := f.commentsReader(strings.TrimPrefix(target, "/")); d != nil {
sheetComments := []Comment{}
var sheetComments []Comment
for _, comment := range d.CommentList.Comment {
sheetComment := Comment{}
if comment.AuthorID < len(d.Authors.Author) {
Expand All @@ -74,7 +74,7 @@ func (f *File) GetComments() (comments map[string][]Comment) {
// getSheetComments provides the method to get the target comment reference by
// given worksheet file path.
func (f *File) getSheetComments(sheetFile string) string {
var rels = "xl/worksheets/_rels/" + sheetFile + ".rels"
rels := "xl/worksheets/_rels/" + sheetFile + ".rels"
if sheetRels := f.relsReader(rels); sheetRels != nil {
sheetRels.Lock()
defer sheetRels.Unlock()
Expand Down Expand Up @@ -256,7 +256,7 @@ func (f *File) addComment(commentsXML, cell string, formatSet *formatComment) {
if comments == nil {
comments = &xlsxComments{Authors: xlsxAuthor{Author: []string{formatSet.Author}}}
}
if inStrSlice(comments.Authors.Author, formatSet.Author) == -1 {
if inStrSlice(comments.Authors.Author, formatSet.Author, true) == -1 {
comments.Authors.Author = append(comments.Authors.Author, formatSet.Author)
authorID = len(comments.Authors.Author) - 1
}
Expand Down
17 changes: 9 additions & 8 deletions comment_test.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2022 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 providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excelâ„¢ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.15 or later.

package excelize

import (
"encoding/xml"
"path/filepath"
"strings"
"testing"
Expand All @@ -38,7 +39,7 @@ func TestAddComments(t *testing.T) {
}

f.Comments["xl/comments2.xml"] = nil
f.Pkg.Store("xl/comments2.xml", []byte(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?><comments xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><authors><author>Excelize: </author></authors><commentList><comment ref="B7" authorId="0"><text><t>Excelize: </t></text></comment></commentList></comments>`))
f.Pkg.Store("xl/comments2.xml", []byte(xml.Header+`<comments xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><authors><author>Excelize: </author></authors><commentList><comment ref="B7" authorId="0"><text><t>Excelize: </t></text></comment></commentList></comments>`))
comments := f.GetComments()
assert.EqualValues(t, 2, len(comments["Sheet1"]))
assert.EqualValues(t, 1, len(comments["Sheet2"]))
Expand Down
109 changes: 79 additions & 30 deletions crypt.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2022 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 providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excelâ„¢ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.15 or later.

package excelize

Expand Down Expand Up @@ -43,6 +43,7 @@ var (
packageOffset = 8 // First 8 bytes are the size of the stream
packageEncryptionChunkSize = 4096
iterCount = 50000
sheetProtectionSpinCount = 1e5
oleIdentifier = []byte{
0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1,
}
Expand Down Expand Up @@ -127,7 +128,7 @@ type StandardEncryptionVerifier struct {
EncryptedVerifierHash []byte
}

// Decrypt API decrypt the CFB file format with ECMA-376 agile encryption and
// Decrypt API decrypts 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) {
Expand All @@ -146,7 +147,7 @@ func Decrypt(raw []byte, opt *Options) (packageBuf []byte, err error) {
case "standard":
return standardDecrypt(encryptionInfoBuf, encryptedPackageBuf, opt)
default:
err = ErrUnsupportEncryptMechanism
err = ErrUnsupportedEncryptMechanism
}
return
}
Expand All @@ -167,16 +168,20 @@ func Encrypt(raw []byte, opt *Options) (packageBuf []byte, err error) {
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)},
}}},
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),
},
},
}},
},
}

Expand Down Expand Up @@ -307,7 +312,7 @@ func encryptionMechanism(buffer []byte) (mechanism string, err error) {
} else if (versionMajor == 3 || versionMajor == 4) && versionMinor == 3 {
mechanism = "extensible"
}
err = ErrUnsupportEncryptMechanism
err = ErrUnsupportedEncryptMechanism
return
}

Expand Down Expand Up @@ -387,14 +392,14 @@ func standardConvertPasswdToKey(header StandardEncryptionHeader, verifier Standa
key = hashing("sha1", iterator, key)
}
var block int
hfinal := hashing("sha1", key, createUInt32LEBuffer(block, 4))
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:]...)
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:]...)
buf2 = append(standardXORBytes(hFinal, buf2[:cbHash]), buf2[cbHash:]...)
x2 := hashing("sha1", buf2)
x3 := append(x1, x2...)
keyDerived := x3[:cbRequiredKeyLength]
Expand All @@ -417,7 +422,8 @@ func standardXORBytes(a, b []byte) []byte {
// 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.
// 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 {
Expand Down Expand Up @@ -479,7 +485,7 @@ func convertPasswdToKey(passwd string, blockKey []byte, encryption Encryption) (

// hashing data by specified hash algorithm.
func hashing(hashAlgorithm string, buffer ...[]byte) (key []byte) {
var hashMap = map[string]hash.Hash{
hashMap := map[string]hash.Hash{
"md4": md4.New(),
"md5": md5.New(),
"ripemd-160": ripemd160.New(),
Expand Down Expand Up @@ -533,8 +539,7 @@ func crypt(encrypt bool, cipherAlgorithm, cipherChaining string, key, iv, input
// 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
encryptedKey, offset := encryption.KeyData, packageOffset
if encrypt {
offset = 0
}
Expand Down Expand Up @@ -605,11 +610,55 @@ func createIV(blockKey interface{}, encryption Encryption) ([]byte, error) {
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.
// 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
}

// ISO Write Protection Method

// genISOPasswdHash implements the ISO password hashing algorithm by given
// plaintext password, name of the cryptographic hash algorithm, salt value
// and spin count.
func genISOPasswdHash(passwd, hashAlgorithm, salt string, spinCount int) (hashValue, saltValue string, err error) {
if len(passwd) < 1 || len(passwd) > MaxFieldLength {
err = ErrPasswordLengthInvalid
return
}
algorithmName, ok := map[string]string{
"MD4": "md4",
"MD5": "md5",
"SHA-1": "sha1",
"SHA-256": "sha256",
"SHA-384": "sha384",
"SHA-512": "sha512",
}[hashAlgorithm]
if !ok {
err = ErrUnsupportedHashAlgorithm
return
}
var b bytes.Buffer
s, _ := randomBytes(16)
if salt != "" {
if s, err = base64.StdEncoding.DecodeString(salt); err != nil {
return
}
}
b.Write(s)
encoder := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewEncoder()
passwordBuffer, _ := encoder.Bytes([]byte(passwd))
b.Write(passwordBuffer)
// Generate the initial hash.
key := hashing(algorithmName, b.Bytes())
// Now regenerate until spin count.
for i := 0; i < spinCount; i++ {
iterator := createUInt32LEBuffer(i, 4)
key = hashing(algorithmName, key, iterator)
}
hashValue, saltValue = base64.StdEncoding.EncodeToString(key), base64.StdEncoding.EncodeToString(s)
return
}
34 changes: 25 additions & 9 deletions crypt_test.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2022 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 providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excelâ„¢ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.15 or later.

package excelize

Expand All @@ -28,11 +28,27 @@ func TestEncrypt(t *testing.T) {
func TestEncryptionMechanism(t *testing.T) {
mechanism, err := encryptionMechanism([]byte{3, 0, 3, 0})
assert.Equal(t, mechanism, "extensible")
assert.EqualError(t, err, ErrUnsupportEncryptMechanism.Error())
assert.EqualError(t, err, ErrUnsupportedEncryptMechanism.Error())
_, err = encryptionMechanism([]byte{})
assert.EqualError(t, err, ErrUnknownEncryptMechanism.Error())
}

func TestHashing(t *testing.T) {
assert.Equal(t, hashing("unsupportHashAlgorithm", []byte{}), []uint8([]byte(nil)))
assert.Equal(t, hashing("unsupportedHashAlgorithm", []byte{}), []byte(nil))
}

func TestGenISOPasswdHash(t *testing.T) {
for hashAlgorithm, expected := range map[string][]string{
"MD4": {"2lZQZUubVHLm/t6KsuHX4w==", "TTHjJdU70B/6Zq83XGhHVA=="},
"MD5": {"HWbqyd4dKKCjk1fEhk2kuQ==", "8ADyorkumWCayIukRhlVKQ=="},
"SHA-1": {"XErQIV3Ol+nhXkyCxrLTEQm+mSc=", "I3nDtyf59ASaNX1l6KpFnA=="},
"SHA-256": {"7oqMFyfED+mPrzRIBQ+KpKT4SClMHEPOZldliP15xAA=", "ru1R/w3P3Jna2Qo+EE8QiA=="},
"SHA-384": {"nMODLlxsC8vr0btcq0kp/jksg5FaI3az5Sjo1yZk+/x4bFzsuIvpDKUhJGAk/fzo", "Zjq9/jHlgOY6MzFDSlVNZg=="},
"SHA-512": {"YZ6jrGOFQgVKK3rDK/0SHGGgxEmFJglQIIRamZc2PkxVtUBp54fQn96+jVXEOqo6dtCSanqksXGcm/h3KaiR4Q==", "p5s/bybHBPtusI7EydTIrg=="},
} {
hashValue, saltValue, err := genISOPasswdHash("password", hashAlgorithm, expected[1], int(sheetProtectionSpinCount))
assert.NoError(t, err)
assert.Equal(t, expected[0], hashValue)
assert.Equal(t, expected[1], saltValue)
}
}
75 changes: 35 additions & 40 deletions datavalidation.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2022 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 providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excelâ„¢ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.15 or later.

package excelize

Expand All @@ -29,7 +29,7 @@ const (
DataValidationTypeDate
DataValidationTypeDecimal
typeList // inline use
DataValidationTypeTextLeng
DataValidationTypeTextLength
DataValidationTypeTime
// DataValidationTypeWhole Integer
DataValidationTypeWhole
Expand Down Expand Up @@ -116,7 +116,7 @@ func (dd *DataValidation) SetInput(title, msg string) {
func (dd *DataValidation) SetDropList(keys []string) error {
formula := strings.Join(keys, ",")
if MaxFieldLength < len(utf16.Encode([]rune(formula))) {
return ErrDataValidationFormulaLenth
return ErrDataValidationFormulaLength
}
dd.Formula1 = fmt.Sprintf(`<formula1>"%s"</formula1>`, formulaEscaper.Replace(formula))
dd.Type = convDataValidationType(typeList)
Expand All @@ -129,33 +129,33 @@ func (dd *DataValidation) SetRange(f1, f2 interface{}, t DataValidationType, o D
var formula1, formula2 string
switch v := f1.(type) {
case int:
formula1 = fmt.Sprintf("<formula1>%d</formula1>", int(v))
formula1 = fmt.Sprintf("<formula1>%d</formula1>", v)
case float64:
if math.Abs(float64(v)) > math.MaxFloat32 {
if math.Abs(v) > math.MaxFloat32 {
return ErrDataValidationRange
}
formula1 = fmt.Sprintf("<formula1>%.17g</formula1>", float64(v))
formula1 = fmt.Sprintf("<formula1>%.17g</formula1>", v)
case string:
formula1 = fmt.Sprintf("<formula1>%s</formula1>", string(v))
formula1 = fmt.Sprintf("<formula1>%s</formula1>", v)
default:
return ErrParameterInvalid
}
switch v := f2.(type) {
case int:
formula2 = fmt.Sprintf("<formula2>%d</formula2>", int(v))
formula2 = fmt.Sprintf("<formula2>%d</formula2>", v)
case float64:
if math.Abs(float64(v)) > math.MaxFloat32 {
if math.Abs(v) > math.MaxFloat32 {
return ErrDataValidationRange
}
formula2 = fmt.Sprintf("<formula2>%.17g</formula2>", float64(v))
formula2 = fmt.Sprintf("<formula2>%.17g</formula2>", v)
case string:
formula2 = fmt.Sprintf("<formula2>%s</formula2>", string(v))
formula2 = fmt.Sprintf("<formula2>%s</formula2>", v)
default:
return ErrParameterInvalid
}
dd.Formula1, dd.Formula2 = formula1, formula2
dd.Type = convDataValidationType(t)
dd.Operator = convDataValidationOperatior(o)
dd.Operator = convDataValidationOperator(o)
return nil
}

Expand All @@ -168,16 +168,12 @@ func (dd *DataValidation) SetRange(f1, f2 interface{}, t DataValidationType, o D
//
// dvRange := excelize.NewDataValidation(true)
// dvRange.Sqref = "A7:B8"
// dvRange.SetSqrefDropList("$E$1:$E$3", true)
// dvRange.SetSqrefDropList("$E$1:$E$3")
// f.AddDataValidation("Sheet1", dvRange)
//
func (dd *DataValidation) SetSqrefDropList(sqref string, isCurrentSheet bool) error {
if isCurrentSheet {
dd.Formula1 = fmt.Sprintf("<formula1>%s</formula1>", sqref)
dd.Type = convDataValidationType(typeList)
return nil
}
return fmt.Errorf("cross-sheet sqref cell are not supported")
func (dd *DataValidation) SetSqrefDropList(sqref string) {
dd.Formula1 = fmt.Sprintf("<formula1>%s</formula1>", sqref)
dd.Type = convDataValidationType(typeList)
}

// SetSqref provides function to set data validation range in drop list.
Expand All @@ -192,22 +188,21 @@ func (dd *DataValidation) SetSqref(sqref string) {
// convDataValidationType get excel data validation type.
func convDataValidationType(t DataValidationType) string {
typeMap := map[DataValidationType]string{
typeNone: "none",
DataValidationTypeCustom: "custom",
DataValidationTypeDate: "date",
DataValidationTypeDecimal: "decimal",
typeList: "list",
DataValidationTypeTextLeng: "textLength",
DataValidationTypeTime: "time",
DataValidationTypeWhole: "whole",
typeNone: "none",
DataValidationTypeCustom: "custom",
DataValidationTypeDate: "date",
DataValidationTypeDecimal: "decimal",
typeList: "list",
DataValidationTypeTextLength: "textLength",
DataValidationTypeTime: "time",
DataValidationTypeWhole: "whole",
}

return typeMap[t]

}

// convDataValidationOperatior get excel data validation operator.
func convDataValidationOperatior(o DataValidationOperator) string {
// convDataValidationOperator get excel data validation operator.
func convDataValidationOperator(o DataValidationOperator) string {
typeMap := map[DataValidationOperator]string{
DataValidationOperatorBetween: "between",
DataValidationOperatorEqual: "equal",
Expand All @@ -220,7 +215,6 @@ func convDataValidationOperatior(o DataValidationOperator) string {
}

return typeMap[o]

}

// AddDataValidation provides set data validation on a range of the worksheet
Expand Down Expand Up @@ -283,7 +277,7 @@ func (f *File) DeleteDataValidation(sheet, sqref string) error {
}
dv := ws.DataValidations
for i := 0; i < len(dv.DataValidation); i++ {
applySqref := []string{}
var applySqref []string
colCells, err := f.flatSqref(dv.DataValidation[i].Sqref)
if err != nil {
return err
Expand Down Expand Up @@ -320,7 +314,8 @@ func (f *File) squashSqref(cells [][]int) []string {
} else if len(cells) == 0 {
return []string{}
}
l, r, res := 0, 0, []string{}
var res []string
l, r := 0, 0
for i := 1; i < len(cells); i++ {
if cells[i][0] == cells[r][0] && cells[i][1]-cells[r][1] > 1 {
curr, _ := f.coordinatesToAreaRef(append(cells[l], cells[r]...))
Expand Down
25 changes: 11 additions & 14 deletions datavalidation_test.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2022 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 providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excelâ„¢ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.15 or later.

package excelize

Expand Down Expand Up @@ -81,20 +81,17 @@ func TestDataValidationError(t *testing.T) {
dvRange := NewDataValidation(true)
dvRange.SetSqref("A7:B8")
dvRange.SetSqref("A7:B8")
assert.NoError(t, dvRange.SetSqrefDropList("$E$1:$E$3", true))

err := dvRange.SetSqrefDropList("$E$1:$E$3", false)
assert.EqualError(t, err, "cross-sheet sqref cell are not supported")
dvRange.SetSqrefDropList("$E$1:$E$3")

assert.NoError(t, f.AddDataValidation("Sheet1", dvRange))

dvRange = NewDataValidation(true)
err = dvRange.SetDropList(make([]string, 258))
err := dvRange.SetDropList(make([]string, 258))
if dvRange.Formula1 != "" {
t.Errorf("data validation error. Formula1 must be empty!")
return
}
assert.EqualError(t, err, ErrDataValidationFormulaLenth.Error())
assert.EqualError(t, err, ErrDataValidationFormulaLength.Error())
assert.EqualError(t, dvRange.SetRange(nil, 20, DataValidationTypeWhole, DataValidationOperatorBetween), ErrParameterInvalid.Error())
assert.EqualError(t, dvRange.SetRange(10, nil, DataValidationTypeWhole, DataValidationOperatorBetween), ErrParameterInvalid.Error())
assert.NoError(t, dvRange.SetRange(10, 20, DataValidationTypeWhole, DataValidationOperatorGreaterThan))
Expand All @@ -114,7 +111,7 @@ func TestDataValidationError(t *testing.T) {
err = dvRange.SetDropList(keys)
assert.Equal(t, prevFormula1, dvRange.Formula1,
"Formula1 should be unchanged for invalid input %v", keys)
assert.EqualError(t, err, ErrDataValidationFormulaLenth.Error())
assert.EqualError(t, err, ErrDataValidationFormulaLength.Error())
}
assert.NoError(t, f.AddDataValidation("Sheet1", dvRange))
assert.NoError(t, dvRange.SetRange(
Expand Down
19 changes: 9 additions & 10 deletions date.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2022 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 providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excelâ„¢ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.15 or later.

package excelize

Expand Down Expand Up @@ -82,7 +82,6 @@ func shiftJulianToNoon(julianDays, julianFraction float64) (float64, float64) {
// minutes, seconds and nanoseconds that comprised a given fraction of a day.
// values would round to 1 us.
func fractionOfADay(fraction float64) (hours, minutes, seconds, nanoseconds int) {

const (
c1us = 1e3
c1s = 1e9
Expand Down Expand Up @@ -137,7 +136,7 @@ func doTheFliegelAndVanFlandernAlgorithm(jd int) (day, month, year int) {
// representation (stored as a floating point number) to a time.Time.
func timeFromExcelTime(excelTime float64, date1904 bool) time.Time {
var date time.Time
var wholeDaysPart = int(excelTime)
wholeDaysPart := int(excelTime)
// Excel uses Julian dates prior to March 1st 1900, and Gregorian
// thereafter.
if wholeDaysPart <= 61 {
Expand All @@ -152,7 +151,7 @@ func timeFromExcelTime(excelTime float64, date1904 bool) time.Time {
}
return date
}
var floatPart = excelTime - float64(wholeDaysPart) + roundEpsilon
floatPart := excelTime - float64(wholeDaysPart) + roundEpsilon
if date1904 {
date = excel1904Epoc
} else {
Expand Down
35 changes: 17 additions & 18 deletions docProps.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2022 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 providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excelâ„¢ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.15 or later.

package excelize

Expand Down Expand Up @@ -75,7 +75,7 @@ func (f *File) SetAppProps(appProperties *AppProperties) (err error) {
field string
)
app = new(xlsxProperties)
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(dafaultXMLPathDocPropsApp)))).
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathDocPropsApp)))).
Decode(app); err != nil && err != io.EOF {
err = fmt.Errorf("xml decode error: %s", err)
return
Expand All @@ -95,14 +95,14 @@ func (f *File) SetAppProps(appProperties *AppProperties) (err error) {
}
app.Vt = NameSpaceDocumentPropertiesVariantTypes.Value
output, err = xml.Marshal(app)
f.saveFileList(dafaultXMLPathDocPropsApp, output)
f.saveFileList(defaultXMLPathDocPropsApp, output)
return
}

// GetAppProps provides a function to get document application properties.
func (f *File) GetAppProps() (ret *AppProperties, err error) {
var app = new(xlsxProperties)
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(dafaultXMLPathDocPropsApp)))).
app := new(xlsxProperties)
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathDocPropsApp)))).
Decode(app); err != nil && err != io.EOF {
err = fmt.Errorf("xml decode error: %s", err)
return
Expand Down Expand Up @@ -181,15 +181,15 @@ func (f *File) SetDocProps(docProperties *DocProperties) (err error) {
)

core = new(decodeCoreProperties)
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(dafaultXMLPathDocPropsCore)))).
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathDocPropsCore)))).
Decode(core); err != nil && err != io.EOF {
err = fmt.Errorf("xml decode error: %s", err)
return
}
newProps, err = &xlsxCoreProperties{
Dc: NameSpaceDublinCore,
Dcterms: NameSpaceDublinCoreTerms,
Dcmitype: NameSpaceDublinCoreMetadataIntiative,
Dcmitype: NameSpaceDublinCoreMetadataInitiative,
XSI: NameSpaceXMLSchemaInstance,
Title: core.Title,
Subject: core.Subject,
Expand All @@ -204,8 +204,7 @@ func (f *File) SetDocProps(docProperties *DocProperties) (err error) {
Category: core.Category,
Version: core.Version,
}, nil
newProps.Created.Text, newProps.Created.Type, newProps.Modified.Text, newProps.Modified.Type =
core.Created.Text, core.Created.Type, core.Modified.Text, core.Modified.Type
newProps.Created.Text, newProps.Created.Type, newProps.Modified.Text, newProps.Modified.Type = core.Created.Text, core.Created.Type, core.Modified.Text, core.Modified.Type
fields = []string{
"Category", "ContentStatus", "Creator", "Description", "Identifier", "Keywords",
"LastModifiedBy", "Revision", "Subject", "Title", "Language", "Version",
Expand All @@ -223,16 +222,16 @@ func (f *File) SetDocProps(docProperties *DocProperties) (err error) {
newProps.Modified.Text = docProperties.Modified
}
output, err = xml.Marshal(newProps)
f.saveFileList(dafaultXMLPathDocPropsCore, output)
f.saveFileList(defaultXMLPathDocPropsCore, output)

return
}

// GetDocProps provides a function to get document core properties.
func (f *File) GetDocProps() (ret *DocProperties, err error) {
var core = new(decodeCoreProperties)
core := new(decodeCoreProperties)

if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(dafaultXMLPathDocPropsCore)))).
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathDocPropsCore)))).
Decode(core); err != nil && err != io.EOF {
err = fmt.Errorf("xml decode error: %s", err)
return
Expand Down
30 changes: 15 additions & 15 deletions docProps_test.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2022 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 providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excelâ„¢ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.15 or later.

package excelize

Expand Down Expand Up @@ -35,13 +35,13 @@ func TestSetAppProps(t *testing.T) {
AppVersion: "16.0000",
}))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetAppProps.xlsx")))
f.Pkg.Store(dafaultXMLPathDocPropsApp, nil)
f.Pkg.Store(defaultXMLPathDocPropsApp, nil)
assert.NoError(t, f.SetAppProps(&AppProperties{}))
assert.NoError(t, f.Close())

// Test unsupported charset
f = NewFile()
f.Pkg.Store(dafaultXMLPathDocPropsApp, MacintoshCyrillicCharset)
f.Pkg.Store(defaultXMLPathDocPropsApp, MacintoshCyrillicCharset)
assert.EqualError(t, f.SetAppProps(&AppProperties{}), "xml decode error: XML syntax error on line 1: invalid UTF-8")
}

Expand All @@ -53,14 +53,14 @@ func TestGetAppProps(t *testing.T) {
props, err := f.GetAppProps()
assert.NoError(t, err)
assert.Equal(t, props.Application, "Microsoft Macintosh Excel")
f.Pkg.Store(dafaultXMLPathDocPropsApp, nil)
f.Pkg.Store(defaultXMLPathDocPropsApp, nil)
_, err = f.GetAppProps()
assert.NoError(t, err)
assert.NoError(t, f.Close())

// Test unsupported charset
f = NewFile()
f.Pkg.Store(dafaultXMLPathDocPropsApp, MacintoshCyrillicCharset)
f.Pkg.Store(defaultXMLPathDocPropsApp, MacintoshCyrillicCharset)
_, err = f.GetAppProps()
assert.EqualError(t, err, "xml decode error: XML syntax error on line 1: invalid UTF-8")
}
Expand All @@ -87,13 +87,13 @@ func TestSetDocProps(t *testing.T) {
Version: "1.0.0",
}))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetDocProps.xlsx")))
f.Pkg.Store(dafaultXMLPathDocPropsCore, nil)
f.Pkg.Store(defaultXMLPathDocPropsCore, nil)
assert.NoError(t, f.SetDocProps(&DocProperties{}))
assert.NoError(t, f.Close())

// Test unsupported charset
f = NewFile()
f.Pkg.Store(dafaultXMLPathDocPropsCore, MacintoshCyrillicCharset)
f.Pkg.Store(defaultXMLPathDocPropsCore, MacintoshCyrillicCharset)
assert.EqualError(t, f.SetDocProps(&DocProperties{}), "xml decode error: XML syntax error on line 1: invalid UTF-8")
}

Expand All @@ -105,14 +105,14 @@ func TestGetDocProps(t *testing.T) {
props, err := f.GetDocProps()
assert.NoError(t, err)
assert.Equal(t, props.Creator, "Microsoft Office User")
f.Pkg.Store(dafaultXMLPathDocPropsCore, nil)
f.Pkg.Store(defaultXMLPathDocPropsCore, nil)
_, err = f.GetDocProps()
assert.NoError(t, err)
assert.NoError(t, f.Close())

// Test unsupported charset
f = NewFile()
f.Pkg.Store(dafaultXMLPathDocPropsCore, MacintoshCyrillicCharset)
f.Pkg.Store(defaultXMLPathDocPropsCore, MacintoshCyrillicCharset)
_, err = f.GetDocProps()
assert.EqualError(t, err, "xml decode error: XML syntax error on line 1: invalid UTF-8")
}
74 changes: 43 additions & 31 deletions drawing.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2022 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 providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excelâ„¢ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.15 or later.

package excelize

Expand Down Expand Up @@ -157,7 +157,8 @@ func (f *File) addChart(formatSet *formatChart, comboCharts []*formatChart) {
Cmpd: "sng",
Algn: "ctr",
SolidFill: &aSolidFill{
SchemeClr: &aSchemeClr{Val: "tx1",
SchemeClr: &aSchemeClr{
Val: "tx1",
LumMod: &attrValInt{
Val: intPtr(15000),
},
Expand Down Expand Up @@ -512,13 +513,18 @@ func (f *File) drawBaseChart(formatSet *formatChart) *cPlotArea {
// drawDoughnutChart provides a function to draw the c:plotArea element for
// doughnut chart by given format sets.
func (f *File) drawDoughnutChart(formatSet *formatChart) *cPlotArea {
holeSize := 75
if formatSet.HoleSize > 0 && formatSet.HoleSize <= 90 {
holeSize = formatSet.HoleSize
}

return &cPlotArea{
DoughnutChart: &cCharts{
VaryColors: &attrValBool{
Val: boolPtr(formatSet.VaryColors),
},
Ser: f.drawChartSeries(formatSet),
HoleSize: &attrValInt{Val: intPtr(75)},
HoleSize: &attrValInt{Val: intPtr(holeSize)},
},
}
}
Expand Down Expand Up @@ -734,7 +740,7 @@ func (f *File) drawChartShape(formatSet *formatChart) *attrValString {
// drawChartSeries provides a function to draw the c:ser element by given
// format sets.
func (f *File) drawChartSeries(formatSet *formatChart) *[]cSer {
ser := []cSer{}
var ser []cSer
for k := range formatSet.Series {
ser = append(ser, cSer{
IDx: &attrValInt{Val: intPtr(k + formatSet.order)},
Expand Down Expand Up @@ -941,7 +947,8 @@ func (f *File) drawChartDLbls(formatSet *formatChart) *cDLbls {
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}
Scatter: nil, Surface3D: nil, WireframeSurface3D: nil, Contour: nil, WireframeContour: nil, Bubble: nil, Bubble3D: nil,
}
if _, ok := chartSeriesDLbls[formatSet.Type]; ok {
return nil
}
Expand All @@ -950,14 +957,14 @@ func (f *File) drawChartSeriesDLbls(formatSet *formatChart) *cDLbls {

// drawPlotAreaCatAx provides a function to draw the c:catAx element.
func (f *File) drawPlotAreaCatAx(formatSet *formatChart) []*cAxs {
min := &attrValFloat{Val: float64Ptr(formatSet.XAxis.Minimum)}
max := &attrValFloat{Val: float64Ptr(formatSet.XAxis.Maximum)}
if formatSet.XAxis.Minimum == 0 {
min = nil
}
if formatSet.XAxis.Maximum == 0 {
max := &attrValFloat{Val: formatSet.XAxis.Maximum}
min := &attrValFloat{Val: formatSet.XAxis.Minimum}
if formatSet.XAxis.Maximum == nil {
max = nil
}
if formatSet.XAxis.Minimum == nil {
min = nil
}
axs := []*cAxs{
{
AxID: &attrValInt{Val: intPtr(754001152)},
Expand Down Expand Up @@ -999,14 +1006,14 @@ func (f *File) drawPlotAreaCatAx(formatSet *formatChart) []*cAxs {

// drawPlotAreaValAx provides a function to draw the c:valAx element.
func (f *File) drawPlotAreaValAx(formatSet *formatChart) []*cAxs {
min := &attrValFloat{Val: float64Ptr(formatSet.YAxis.Minimum)}
max := &attrValFloat{Val: float64Ptr(formatSet.YAxis.Maximum)}
if formatSet.YAxis.Minimum == 0 {
min = nil
}
if formatSet.YAxis.Maximum == 0 {
max := &attrValFloat{Val: formatSet.YAxis.Maximum}
min := &attrValFloat{Val: formatSet.YAxis.Minimum}
if formatSet.YAxis.Maximum == nil {
max = nil
}
if formatSet.YAxis.Minimum == nil {
min = nil
}
var logBase *attrValFloat
if formatSet.YAxis.LogBase >= 2 && formatSet.YAxis.LogBase <= 1000 {
logBase = &attrValFloat{Val: float64Ptr(formatSet.YAxis.LogBase)}
Expand Down Expand Up @@ -1053,14 +1060,14 @@ func (f *File) drawPlotAreaValAx(formatSet *formatChart) []*cAxs {

// drawPlotAreaSerAx provides a function to draw the c:serAx element.
func (f *File) drawPlotAreaSerAx(formatSet *formatChart) []*cAxs {
min := &attrValFloat{Val: float64Ptr(formatSet.YAxis.Minimum)}
max := &attrValFloat{Val: float64Ptr(formatSet.YAxis.Maximum)}
if formatSet.YAxis.Minimum == 0 {
min = nil
}
if formatSet.YAxis.Maximum == 0 {
max := &attrValFloat{Val: formatSet.YAxis.Maximum}
min := &attrValFloat{Val: formatSet.YAxis.Minimum}
if formatSet.YAxis.Maximum == nil {
max = nil
}
if formatSet.YAxis.Minimum == nil {
min = nil
}
return []*cAxs{
{
AxID: &attrValInt{Val: intPtr(832256642)},
Expand Down Expand Up @@ -1158,6 +1165,12 @@ func (f *File) drawingParser(path string) (*xlsxWsDr, int) {
log.Printf("xml decode error: %s", err)
}
content.R = decodeWsDr.R
for _, v := range decodeWsDr.AlternateContent {
content.AlternateContent = append(content.AlternateContent, &xlsxAlternateContent{
Content: v.Content,
XMLNSMC: SourceRelationshipCompatibility.Value,
})
}
for _, v := range decodeWsDr.OneCellAnchor {
content.OneCellAnchor = append(content.OneCellAnchor, &xdrCellAnchor{
EditAs: v.EditAs,
Expand Down Expand Up @@ -1194,8 +1207,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 :=
f.positionObjectPixels(sheet, colIdx, rowIdx, formatSet.OffsetX, formatSet.OffsetY, width, height)
colStart, rowStart, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, colIdx, rowIdx, formatSet.OffsetX, formatSet.OffsetY, width, height)
content, cNvPrID := f.drawingParser(drawingXML)
twoCellAnchor := xdrCellAnchor{}
twoCellAnchor.EditAs = formatSet.Positioning
Expand Down
21 changes: 13 additions & 8 deletions drawing_test.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2022 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 providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excelâ„¢ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.15 or later.

package excelize

import (
"encoding/xml"
"sync"
"testing"
)
Expand All @@ -22,9 +23,13 @@ func TestDrawingParser(t *testing.T) {
Pkg: sync.Map{},
}
f.Pkg.Store("charset", MacintoshCyrillicCharset)
f.Pkg.Store("wsDr", []byte(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?><xdr:wsDr xmlns:xdr="http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"><xdr:oneCellAnchor><xdr:graphicFrame/></xdr:oneCellAnchor></xdr:wsDr>`))
f.Pkg.Store("wsDr", []byte(xml.Header+`<xdr:wsDr xmlns:xdr="http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"><xdr:oneCellAnchor><xdr:graphicFrame/></xdr:oneCellAnchor></xdr:wsDr>`))
// Test with one cell anchor
f.drawingParser("wsDr")
// Test with unsupported charset
f.drawingParser("charset")
// Test with alternate content
f.Drawings = sync.Map{}
f.Pkg.Store("wsDr", []byte(xml.Header+`<xdr:wsDr xmlns:xdr="http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"><mc:AlternateContent xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"><mc:Choice xmlns:a14="http://schemas.microsoft.com/office/drawing/2010/main" Requires="a14"><xdr:twoCellAnchor editAs="oneCell"></xdr:twoCellAnchor></mc:Choice><mc:Fallback/></mc:AlternateContent></xdr:wsDr>`))
f.drawingParser("wsDr")
}
74 changes: 50 additions & 24 deletions errors.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2022 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 providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excelâ„¢ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.15 or later.

package excelize

Expand All @@ -16,42 +16,50 @@ import (
"fmt"
)

// newInvalidColumnNameError defined the error message on receiving the invalid column name.
// newInvalidColumnNameError defined the error message on receiving the
// invalid column name.
func newInvalidColumnNameError(col string) error {
return fmt.Errorf("invalid column name %q", col)
}

// newInvalidRowNumberError defined the error message on receiving the invalid row number.
// newInvalidRowNumberError defined the error message on receiving the invalid
// row number.
func newInvalidRowNumberError(row int) error {
return fmt.Errorf("invalid row number %d", row)
}

// newInvalidCellNameError defined the error message on receiving the invalid cell name.
// newInvalidCellNameError defined the error message on receiving the invalid
// cell name.
func newInvalidCellNameError(cell string) error {
return fmt.Errorf("invalid cell name %q", cell)
}

// newInvalidExcelDateError defined the error message on receiving the data with negative values.
// newInvalidExcelDateError defined the error message on receiving the data
// with negative values.
func newInvalidExcelDateError(dateValue float64) error {
return fmt.Errorf("invalid date value %f, negative values are not supported", dateValue)
}

// newUnsupportChartType defined the error message on receiving the chart type are unsupported.
func newUnsupportChartType(chartType string) error {
// newUnsupportedChartType defined the error message on receiving the chart
// type are unsupported.
func newUnsupportedChartType(chartType string) error {
return fmt.Errorf("unsupported chart type %s", chartType)
}

// newUnzipSizeLimitError defined the error message on unzip size exceeds the limit.
// newUnzipSizeLimitError defined the error message on unzip size exceeds the
// limit.
func newUnzipSizeLimitError(unzipSizeLimit int64) error {
return fmt.Errorf("unzip size exceeds the %d bytes limit", unzipSizeLimit)
}

// newInvalidStyleID defined the error message on receiving the invalid style ID.
// newInvalidStyleID defined the error message on receiving the invalid style
// ID.
func newInvalidStyleID(styleID int) error {
return fmt.Errorf("invalid style ID %d, negative values are not supported", styleID)
}

// newFieldLengthError defined the error message on receiving the field length overflow.
// newFieldLengthError defined the error message on receiving the field length
// overflow.
func newFieldLengthError(name string) error {
return fmt.Errorf("field %s must be less or equal than 255 characters", name)
}
Expand Down Expand Up @@ -98,17 +106,29 @@ var (
// ErrImgExt defined the error message on receive an unsupported image
// extension.
ErrImgExt = errors.New("unsupported image extension")
// ErrWorkbookExt defined the error message on receive an unsupported
// workbook extension.
ErrWorkbookExt = errors.New("unsupported workbook extension")
// ErrMaxFileNameLength defined the error message on receive the file name
// length overflow.
ErrMaxFileNameLength = errors.New("file name length exceeds maximum limit")
// ErrEncrypt defined the error message on encryption spreadsheet.
ErrEncrypt = errors.New("not support encryption currently")
// ErrUnknownEncryptMechanism defined the error message on unsupport
// ErrUnknownEncryptMechanism defined the error message on unsupported
// encryption mechanism.
ErrUnknownEncryptMechanism = errors.New("unknown encryption mechanism")
// ErrUnsupportEncryptMechanism defined the error message on unsupport
// ErrUnsupportedEncryptMechanism defined the error message on unsupported
// encryption mechanism.
ErrUnsupportEncryptMechanism = errors.New("unsupport encryption mechanism")
ErrUnsupportedEncryptMechanism = errors.New("unsupported encryption mechanism")
// ErrUnsupportedHashAlgorithm defined the error message on unsupported
// hash algorithm.
ErrUnsupportedHashAlgorithm = errors.New("unsupported hash algorithm")
// ErrUnsupportedNumberFormat defined the error message on unsupported number format
// expression.
ErrUnsupportedNumberFormat = errors.New("unsupported number format token")
// ErrPasswordLengthInvalid defined the error message on invalid password
// length.
ErrPasswordLengthInvalid = errors.New("password length invalid")
// ErrParameterRequired defined the error message on receive the empty
// parameter.
ErrParameterRequired = errors.New("parameter is required")
Expand All @@ -118,9 +138,9 @@ var (
// ErrDefinedNameScope defined the error message on not found defined name
// in the given scope.
ErrDefinedNameScope = errors.New("no defined name on the scope")
// ErrDefinedNameduplicate defined the error message on the same name
// ErrDefinedNameDuplicate defined the error message on the same name
// already exists on the scope.
ErrDefinedNameduplicate = errors.New("the same name already exists on the scope")
ErrDefinedNameDuplicate = errors.New("the same name already exists on the scope")
// ErrCustomNumFmt defined the error message on receive the empty custom number format.
ErrCustomNumFmt = errors.New("custom number format can not be empty")
// ErrFontLength defined the error message on the length of the font
Expand All @@ -131,11 +151,17 @@ var (
// ErrSheetIdx defined the error message on receive the invalid worksheet
// index.
ErrSheetIdx = errors.New("invalid worksheet index")
// ErrUnprotectSheet defined the error message on worksheet has set no
// protection.
ErrUnprotectSheet = errors.New("worksheet has set no protect")
// ErrUnprotectSheetPassword defined the error message on remove sheet
// protection with password verification failed.
ErrUnprotectSheetPassword = errors.New("worksheet protect password not match")
// ErrGroupSheets defined the error message on group sheets.
ErrGroupSheets = errors.New("group worksheet must contain an active worksheet")
// ErrDataValidationFormulaLenth defined the error message for receiving a
// ErrDataValidationFormulaLength defined the error message for receiving a
// data validation formula length that exceeds the limit.
ErrDataValidationFormulaLenth = errors.New("data validation must be 0-255 characters")
ErrDataValidationFormulaLength = errors.New("data validation must be 0-255 characters")
// ErrDataValidationRange defined the error message on set decimal range
// exceeds limit.
ErrDataValidationRange = errors.New("data validation range exceeds limit")
Expand Down Expand Up @@ -164,5 +190,5 @@ var (
ErrSparkline = errors.New("must have the same number of 'Location' and 'Range' parameters")
// ErrSparklineStyle defined the error message on receive the invalid
// sparkline Style parameters.
ErrSparklineStyle = errors.New("parameter 'Style' must betweent 0-35")
ErrSparklineStyle = errors.New("parameter 'Style' must between 0-35")
)
83 changes: 43 additions & 40 deletions excelize.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2022 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 providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excelâ„¢ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.15 or later.
//
// See https://xuri.me/excelize for more information about this package.
package excelize
Expand All @@ -32,30 +32,31 @@ import (
// File define a populated spreadsheet 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
tempFiles sync.Map
CalcChain *xlsxCalcChain
Comments map[string]*xlsxComments
ContentTypes *xlsxTypes
Drawings sync.Map
Path string
SharedStrings *xlsxSST
sharedStringsMap map[string]int
sharedStringItemMap *sync.Map
Sheet sync.Map
SheetCount int
Styles *xlsxStyleSheet
Theme *xlsxTheme
DecodeVMLDrawing map[string]*decodeVmlDrawing
VMLDrawing map[string]*vmlDrawing
WorkBook *xlsxWorkbook
Relationships sync.Map
Pkg sync.Map
CharsetReader charsetTranscoderFn
options *Options
xmlAttr map[string][]xml.Attr
checked map[string]bool
sheetMap map[string]string
streams map[string]*StreamWriter
tempFiles sync.Map
CalcChain *xlsxCalcChain
Comments map[string]*xlsxComments
ContentTypes *xlsxTypes
Drawings sync.Map
Path string
SharedStrings *xlsxSST
sharedStringsMap map[string]int
sharedStringItem [][]uint
sharedStringTemp *os.File
Sheet sync.Map
SheetCount int
Styles *xlsxStyleSheet
Theme *xlsxTheme
DecodeVMLDrawing map[string]*decodeVmlDrawing
VMLDrawing map[string]*vmlDrawing
WorkBook *xlsxWorkbook
Relationships sync.Map
Pkg sync.Map
CharsetReader charsetTranscoderFn
}

type charsetTranscoderFn func(charset string, input io.Reader) (rdr io.Reader, err error)
Expand Down Expand Up @@ -101,13 +102,16 @@ func OpenFile(filename string, opt ...Options) (*File, error) {
if err != nil {
return nil, err
}
defer file.Close()
f, err := OpenReader(file, opt...)
if err != nil {
return nil, err
closeErr := file.Close()
if closeErr == nil {
return f, err
}
return f, closeErr
}
f.Path = filename
return f, nil
return f, file.Close()
}

// newFile is object builder
Expand Down Expand Up @@ -326,7 +330,7 @@ func checkSheetR0(ws *xlsxWorksheet, sheetData *xlsxSheetData, r0 *xlsxRow) {
// 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{
uniqPart := map[string]string{
SourceRelationshipSharedStrings: "/xl/sharedStrings.xml",
}
rels := f.relsReader(relPath)
Expand Down Expand Up @@ -433,7 +437,6 @@ func (f *File) AddVBAProject(bin string) error {
if path.Ext(bin) != ".bin" {
return ErrAddVBAProject
}
f.setContentTypePartVBAProjectExtensions()
wb := f.relsReader(f.getWorkbookRelsPath())
wb.Lock()
defer wb.Unlock()
Expand Down Expand Up @@ -462,9 +465,9 @@ func (f *File) AddVBAProject(bin string) error {
return err
}

// setContentTypePartVBAProjectExtensions provides a function to set the
// content type for relationship parts and the main document part.
func (f *File) setContentTypePartVBAProjectExtensions() {
// setContentTypePartProjectExtensions provides a function to set the content
// type for relationship parts and the main document part.
func (f *File) setContentTypePartProjectExtensions(contentType string) {
var ok bool
content := f.contentTypesReader()
content.Lock()
Expand All @@ -476,7 +479,7 @@ func (f *File) setContentTypePartVBAProjectExtensions() {
}
for idx, o := range content.Overrides {
if o.PartName == "/xl/workbook.xml" {
content.Overrides[idx].ContentType = ContentTypeMacro
content.Overrides[idx].ContentType = contentType
}
}
if !ok {
Expand Down
97 changes: 77 additions & 20 deletions excelize_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,11 @@ func TestOpenFile(t *testing.T) {
}
assert.NoError(t, f.UpdateLinkedValue())

assert.NoError(t, f.SetCellDefault("Sheet2", "A1", strconv.FormatFloat(float64(100.1588), 'f', -1, 32)))
assert.NoError(t, f.SetCellDefault("Sheet2", "A1", strconv.FormatFloat(float64(-100.1588), 'f', -1, 64)))
assert.NoError(t, f.SetCellDefault("Sheet2", "A1", strconv.FormatFloat(100.1588, 'f', -1, 32)))
assert.NoError(t, f.SetCellDefault("Sheet2", "A1", strconv.FormatFloat(-100.1588, 'f', -1, 64)))

// Test set cell value with illegal row number.
assert.EqualError(t, f.SetCellDefault("Sheet2", "A", strconv.FormatFloat(float64(-100.1588), 'f', -1, 64)),
assert.EqualError(t, f.SetCellDefault("Sheet2", "A", strconv.FormatFloat(-100.1588, 'f', -1, 64)),
newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())

assert.NoError(t, f.SetCellInt("Sheet2", "A1", 100))
Expand Down Expand Up @@ -83,7 +83,7 @@ func TestOpenFile(t *testing.T) {
assert.NoError(t, err)
_, err = f.GetCellFormula("Sheet2", "I11")
assert.NoError(t, err)
getSharedForumula(&xlsxWorksheet{}, 0, "")
getSharedFormula(&xlsxWorksheet{}, 0, "")

// Test read cell value with given illegal rows number.
_, err = f.GetCellValue("Sheet2", "a-1")
Expand All @@ -109,7 +109,7 @@ func TestOpenFile(t *testing.T) {
assert.NoError(t, f.SetCellValue("Sheet2", "F5", int32(1<<32/2-1)))
assert.NoError(t, f.SetCellValue("Sheet2", "F6", int64(1<<32/2-1)))
assert.NoError(t, f.SetCellValue("Sheet2", "F7", float32(42.65418)))
assert.NoError(t, f.SetCellValue("Sheet2", "F8", float64(-42.65418)))
assert.NoError(t, f.SetCellValue("Sheet2", "F8", -42.65418))
assert.NoError(t, f.SetCellValue("Sheet2", "F9", float32(42)))
assert.NoError(t, f.SetCellValue("Sheet2", "F10", float64(42)))
assert.NoError(t, f.SetCellValue("Sheet2", "F11", uint(1<<32-1)))
Expand All @@ -130,14 +130,17 @@ func TestOpenFile(t *testing.T) {
// Test boolean write
booltest := []struct {
value bool
raw bool
expected string
}{
{false, "0"},
{true, "1"},
{false, true, "0"},
{true, true, "1"},
{false, false, "FALSE"},
{true, false, "TRUE"},
}
for _, test := range booltest {
assert.NoError(t, f.SetCellValue("Sheet2", "F16", test.value))
val, err := f.GetCellValue("Sheet2", "F16")
val, err := f.GetCellValue("Sheet2", "F16", Options{RawCellValue: test.raw})
assert.NoError(t, err)
assert.Equal(t, test.expected, val)
}
Expand All @@ -147,6 +150,7 @@ func TestOpenFile(t *testing.T) {
assert.NoError(t, f.SetCellValue("Sheet2", "G4", time.Now()))

assert.NoError(t, f.SetCellValue("Sheet2", "G4", time.Now().UTC()))
assert.EqualError(t, f.SetCellValue("SheetN", "A1", time.Now()), "sheet SheetN is not exist")
// 02:46:40
assert.NoError(t, f.SetCellValue("Sheet2", "G5", time.Duration(1e13)))
// Test completion column.
Expand Down Expand Up @@ -175,7 +179,10 @@ func TestSaveFile(t *testing.T) {
if !assert.NoError(t, err) {
t.FailNow()
}
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSaveFile.xlsx")))
assert.EqualError(t, f.SaveAs(filepath.Join("test", "TestSaveFile.xlsb")), ErrWorkbookExt.Error())
for _, ext := range []string{".xlam", ".xlsm", ".xlsx", ".xltm", ".xltx"} {
assert.NoError(t, f.SaveAs(filepath.Join("test", fmt.Sprintf("TestSaveFile%s", ext))))
}
assert.NoError(t, f.Close())
f, err = OpenFile(filepath.Join("test", "TestSaveFile.xlsx"))
if !assert.NoError(t, err) {
Expand All @@ -189,7 +196,7 @@ func TestSaveAsWrongPath(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
assert.NoError(t, err)
// Test write file to not exist directory.
assert.EqualError(t, f.SaveAs(""), "open .: is a directory")
assert.Error(t, f.SaveAs(filepath.Join("x", "Book1.xlsx")))
assert.NoError(t, f.Close())
}

Expand Down Expand Up @@ -224,7 +231,7 @@ func TestOpenReader(t *testing.T) {
assert.Equal(t, "SECRET", val)
assert.NoError(t, f.Close())

// Test open spreadsheet with invalid optioins.
// Test open spreadsheet with invalid options.
_, err = OpenReader(bytes.NewReader(oleIdentifier), Options{UnzipSizeLimit: 1, UnzipXMLSizeLimit: 2})
assert.EqualError(t, err, ErrOptionsUnzipSizeLimit.Error())

Expand Down Expand Up @@ -1065,7 +1072,7 @@ func TestConditionalFormat(t *testing.T) {
// Set conditional format with illegal criteria type.
assert.NoError(t, f.SetConditionalFormat(sheet1, "K1:K10", `[{"type":"data_bar", "criteria":"", "min_type":"min","max_type":"max","bar_color":"#638EC6"}]`))

// Set conditional format with file without dxfs element shold not return error.
// Set conditional format with file without dxfs element should not return error.
f, err = OpenFile(filepath.Join("test", "Book1.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
Expand Down Expand Up @@ -1150,23 +1157,54 @@ func TestHSL(t *testing.T) {
assert.Equal(t, 0.0, 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)
assert.Equal(t, 0.5, h)
assert.Equal(t, 1.0, s)
assert.Equal(t, 0.5, l)
t.Log(RGBToHSL(250, 100, 50))
t.Log(RGBToHSL(50, 100, 250))
t.Log(RGBToHSL(250, 50, 100))
}

func TestProtectSheet(t *testing.T) {
f := NewFile()
assert.NoError(t, f.ProtectSheet("Sheet1", nil))
assert.NoError(t, f.ProtectSheet("Sheet1", &FormatSheetProtection{
sheetName := f.GetSheetName(0)
assert.NoError(t, f.ProtectSheet(sheetName, nil))
// Test protect worksheet with XOR hash algorithm
assert.NoError(t, f.ProtectSheet(sheetName, &FormatSheetProtection{
Password: "password",
EditScenarios: false,
}))

ws, err := f.workSheetReader(sheetName)
assert.NoError(t, err)
assert.Equal(t, "83AF", ws.SheetProtection.Password)
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestProtectSheet.xlsx")))
// Test protect worksheet with SHA-512 hash algorithm
assert.NoError(t, f.ProtectSheet(sheetName, &FormatSheetProtection{
AlgorithmName: "SHA-512",
Password: "password",
}))
ws, err = f.workSheetReader(sheetName)
assert.NoError(t, err)
assert.Equal(t, 24, len(ws.SheetProtection.SaltValue))
assert.Equal(t, 88, len(ws.SheetProtection.HashValue))
assert.Equal(t, int(sheetProtectionSpinCount), ws.SheetProtection.SpinCount)
// Test remove sheet protection with an incorrect password
assert.EqualError(t, f.UnprotectSheet(sheetName, "wrongPassword"), ErrUnprotectSheetPassword.Error())
// Test remove sheet protection with password verification
assert.NoError(t, f.UnprotectSheet(sheetName, "password"))
// Test protect worksheet with empty password
assert.NoError(t, f.ProtectSheet(sheetName, &FormatSheetProtection{}))
assert.Equal(t, "", ws.SheetProtection.Password)
// Test protect worksheet with password exceeds the limit length
assert.EqualError(t, f.ProtectSheet(sheetName, &FormatSheetProtection{
AlgorithmName: "MD4",
Password: strings.Repeat("s", MaxFieldLength+1),
}), ErrPasswordLengthInvalid.Error())
// Test protect worksheet with unsupported hash algorithm
assert.EqualError(t, f.ProtectSheet(sheetName, &FormatSheetProtection{
AlgorithmName: "RIPEMD-160",
Password: "password",
}), ErrUnsupportedHashAlgorithm.Error())
// Test protect not exists worksheet.
assert.EqualError(t, f.ProtectSheet("SheetN", nil), "sheet SheetN is not exist")
}
Expand All @@ -1176,12 +1214,30 @@ func TestUnprotectSheet(t *testing.T) {
if !assert.NoError(t, err) {
t.FailNow()
}
// Test unprotect not exists worksheet.
// Test remove protection on not exists worksheet.
assert.EqualError(t, f.UnprotectSheet("SheetN"), "sheet SheetN is not exist")

assert.NoError(t, f.UnprotectSheet("Sheet1"))
assert.EqualError(t, f.UnprotectSheet("Sheet1", "password"), ErrUnprotectSheet.Error())
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestUnprotectSheet.xlsx")))
assert.NoError(t, f.Close())

f = NewFile()
sheetName := f.GetSheetName(0)
assert.NoError(t, f.ProtectSheet(sheetName, &FormatSheetProtection{Password: "password"}))
// Test remove sheet protection with an incorrect password
assert.EqualError(t, f.UnprotectSheet(sheetName, "wrongPassword"), ErrUnprotectSheetPassword.Error())
// Test remove sheet protection with password verification
assert.NoError(t, f.UnprotectSheet(sheetName, "password"))
// Test with invalid salt value
assert.NoError(t, f.ProtectSheet(sheetName, &FormatSheetProtection{
AlgorithmName: "SHA-512",
Password: "password",
}))
ws, err := f.workSheetReader(sheetName)
assert.NoError(t, err)
ws.SheetProtection.SaltValue = "YWJjZA====="
assert.EqualError(t, f.UnprotectSheet(sheetName, "wrongPassword"), "illegal base64 data at input byte 8")
}

func TestSetDefaultTimeStyle(t *testing.T) {
Expand Down Expand Up @@ -1256,7 +1312,8 @@ func TestDeleteSheetFromWorkbookRels(t *testing.T) {

func TestAttrValToInt(t *testing.T) {
_, err := attrValToInt("r", []xml.Attr{
{Name: xml.Name{Local: "r"}, Value: "s"}})
{Name: xml.Name{Local: "r"}, Value: "s"},
})
assert.EqualError(t, err, `strconv.Atoi: parsing "s": invalid syntax`)
}

Expand Down
Loading