diff --git a/calc.go b/calc.go
index 796ca16..c600aaa 100644
--- a/calc.go
+++ b/calc.go
@@ -339,6 +339,7 @@ type formulaFuncs struct {
// ACOT
// ACOTH
// ADDRESS
+// AGGREGATE
// AMORDEGRC
// AMORLINC
// AND
@@ -700,6 +701,7 @@ type formulaFuncs struct {
// STDEVPA
// STEYX
// SUBSTITUTE
+// SUBTOTAL
// SUM
// SUMIF
// SUMIFS
@@ -872,7 +874,6 @@ func (f *File) evalInfixExp(ctx *calcContext, sheet, cell string, tokens []efp.T
var err error
opdStack, optStack, opfStack, opfdStack, opftStack, argsStack := NewStack(), NewStack(), NewStack(), NewStack(), NewStack(), NewStack()
var inArray, inArrayRow bool
- var arrayRow []formulaArg
for i := 0; i < len(tokens); i++ {
token := tokens[i]
@@ -981,7 +982,6 @@ func (f *File) evalInfixExp(ctx *calcContext, sheet, cell string, tokens []efp.T
argsStack.Peek().(*list.List).PushBack(newStringFormulaArg(token.TValue))
}
if inArrayRow && isOperand(token) {
- arrayRow = append(arrayRow, tokenToFormulaArg(token))
continue
}
if inArrayRow && isFunctionStopToken(token) {
@@ -990,7 +990,7 @@ func (f *File) evalInfixExp(ctx *calcContext, sheet, cell string, tokens []efp.T
}
if inArray && isFunctionStopToken(token) {
argsStack.Peek().(*list.List).PushBack(opfdStack.Pop())
- arrayRow, inArray = []formulaArg{}, false
+ inArray = false
continue
}
if err = f.evalInfixExpFunc(ctx, sheet, cell, token, nextToken, opfStack, opdStack, opftStack, opfdStack, argsStack); err != nil {
@@ -3559,6 +3559,56 @@ func (fn *formulaFuncs) ACOTH(argsList *list.List) formulaArg {
return newNumberFormulaArg(math.Atanh(1 / arg.Number))
}
+// AGGREGATE function returns the result of a specified operation or function,
+// applied to a list or database of values. The syntax of the function is:
+//
+// AGGREGATE(function_num,options,ref1,[ref2],...)
+func (fn *formulaFuncs) AGGREGATE(argsList *list.List) formulaArg {
+ if argsList.Len() < 2 {
+ return newErrorFormulaArg(formulaErrorVALUE, "AGGREGATE requires at least 3 arguments")
+ }
+ var fnNum, opts formulaArg
+ if fnNum = argsList.Front().Value.(formulaArg).ToNumber(); fnNum.Type != ArgNumber {
+ return fnNum
+ }
+ subFn, ok := map[int]func(argsList *list.List) formulaArg{
+ 1: fn.AVERAGE,
+ 2: fn.COUNT,
+ 3: fn.COUNTA,
+ 4: fn.MAX,
+ 5: fn.MIN,
+ 6: fn.PRODUCT,
+ 7: fn.STDEVdotS,
+ 8: fn.STDEVdotP,
+ 9: fn.SUM,
+ 10: fn.VARdotS,
+ 11: fn.VARdotP,
+ 12: fn.MEDIAN,
+ 13: fn.MODEdotSNGL,
+ 14: fn.LARGE,
+ 15: fn.SMALL,
+ 16: fn.PERCENTILEdotINC,
+ 17: fn.QUARTILEdotINC,
+ 18: fn.PERCENTILEdotEXC,
+ 19: fn.QUARTILEdotEXC,
+ }[int(fnNum.Number)]
+ if !ok {
+ return newErrorFormulaArg(formulaErrorVALUE, "AGGREGATE has invalid function_num")
+ }
+ if opts = argsList.Front().Next().Value.(formulaArg).ToNumber(); opts.Type != ArgNumber {
+ return opts
+ }
+ // TODO: apply option argument values to be ignored during the calculation
+ if int(opts.Number) < 0 || int(opts.Number) > 7 {
+ return newErrorFormulaArg(formulaErrorVALUE, "AGGREGATE has invalid options")
+ }
+ subArgList := list.New().Init()
+ for arg := argsList.Front().Next().Next(); arg != nil; arg = arg.Next() {
+ subArgList.PushBack(arg.Value.(formulaArg))
+ }
+ return subFn(subArgList)
+}
+
// ARABIC function converts a Roman numeral into an Arabic numeral. The syntax
// of the function is:
//
@@ -5555,6 +5605,41 @@ func (fn *formulaFuncs) POISSON(argsList *list.List) formulaArg {
return newNumberFormulaArg(math.Exp(0-mean.Number) * math.Pow(mean.Number, x.Number) / fact(x.Number))
}
+// SUBTOTAL function performs a specified calculation (e.g. the sum, product,
+// average, etc.) for a supplied set of values. The syntax of the function is:
+//
+// SUBTOTAL(function_num,ref1,[ref2],...)
+func (fn *formulaFuncs) SUBTOTAL(argsList *list.List) formulaArg {
+ if argsList.Len() < 2 {
+ return newErrorFormulaArg(formulaErrorVALUE, "SUBTOTAL requires at least 2 arguments")
+ }
+ var fnNum formulaArg
+ if fnNum = argsList.Front().Value.(formulaArg).ToNumber(); fnNum.Type != ArgNumber {
+ return fnNum
+ }
+ subFn, ok := map[int]func(argsList *list.List) formulaArg{
+ 1: fn.AVERAGE, 101: fn.AVERAGE,
+ 2: fn.COUNT, 102: fn.COUNT,
+ 3: fn.COUNTA, 103: fn.COUNTA,
+ 4: fn.MAX, 104: fn.MAX,
+ 5: fn.MIN, 105: fn.MIN,
+ 6: fn.PRODUCT, 106: fn.PRODUCT,
+ 7: fn.STDEV, 107: fn.STDEV,
+ 8: fn.STDEVP, 108: fn.STDEVP,
+ 9: fn.SUM, 109: fn.SUM,
+ 10: fn.VAR, 110: fn.VAR,
+ 11: fn.VARP, 111: fn.VARP,
+ }[int(fnNum.Number)]
+ if !ok {
+ return newErrorFormulaArg(formulaErrorVALUE, "SUBTOTAL has invalid function_num")
+ }
+ subArgList := list.New().Init()
+ for arg := argsList.Front().Next(); arg != nil; arg = arg.Next() {
+ subArgList.PushBack(arg.Value.(formulaArg))
+ }
+ return subFn(subArgList)
+}
+
// SUM function adds together a supplied set of numbers and returns the sum of
// these values. The syntax of the function is:
//
@@ -11622,8 +11707,7 @@ func (fn *formulaFuncs) OR(argsList *list.List) formulaArg {
}
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
case ArgNumber:
- or = token.Number != 0
- if or {
+ if or = token.Number != 0; or {
return newStringFormulaArg(strings.ToUpper(strconv.FormatBool(or)))
}
case ArgMatrix:
diff --git a/calc_test.go b/calc_test.go
index ea3f014..1a8b8c6 100644
--- a/calc_test.go
+++ b/calc_test.go
@@ -393,16 +393,34 @@ func TestCalcCellValue(t *testing.T) {
"=ACOSH(2.5)": "1.56679923697241",
"=ACOSH(5)": "2.29243166956118",
"=ACOSH(ACOSH(5))": "1.47138332153668",
- // ACOT
+ // _xlfn.ACOT
"=_xlfn.ACOT(1)": "0.785398163397448",
"=_xlfn.ACOT(-2)": "2.67794504458899",
"=_xlfn.ACOT(0)": "1.5707963267949",
"=_xlfn.ACOT(_xlfn.ACOT(0))": "0.566911504941009",
- // ACOTH
+ // _xlfn.ACOTH
"=_xlfn.ACOTH(-5)": "-0.202732554054082",
"=_xlfn.ACOTH(1.1)": "1.52226121886171",
"=_xlfn.ACOTH(2)": "0.549306144334055",
"=_xlfn.ACOTH(ABS(-2))": "0.549306144334055",
+ // _xlfn.AGGREGATE
+ "=_xlfn.AGGREGATE(1,0,A1:A6)": "1.5",
+ "=_xlfn.AGGREGATE(2,0,A1:A6)": "4",
+ "=_xlfn.AGGREGATE(3,0,A1:A6)": "4",
+ "=_xlfn.AGGREGATE(4,0,A1:A6)": "3",
+ "=_xlfn.AGGREGATE(5,0,A1:A6)": "0",
+ "=_xlfn.AGGREGATE(6,0,A1:A6)": "0",
+ "=_xlfn.AGGREGATE(7,0,A1:A6)": "1.29099444873581",
+ "=_xlfn.AGGREGATE(8,0,A1:A6)": "1.11803398874989",
+ "=_xlfn.AGGREGATE(9,0,A1:A6)": "6",
+ "=_xlfn.AGGREGATE(10,0,A1:A6)": "1.66666666666667",
+ "=_xlfn.AGGREGATE(11,0,A1:A6)": "1.25",
+ "=_xlfn.AGGREGATE(12,0,A1:A6)": "1.5",
+ "=_xlfn.AGGREGATE(14,0,A1:A6,1)": "3",
+ "=_xlfn.AGGREGATE(15,0,A1:A6,1)": "0",
+ "=_xlfn.AGGREGATE(16,0,A1:A6,1)": "3",
+ "=_xlfn.AGGREGATE(17,0,A1:A6,1)": "0.75",
+ "=_xlfn.AGGREGATE(19,0,A1:A6,1)": "0.25",
// ARABIC
"=_xlfn.ARABIC(\"IV\")": "4",
"=_xlfn.ARABIC(\"-IV\")": "-4",
@@ -791,6 +809,31 @@ func TestCalcCellValue(t *testing.T) {
// POISSON
"=POISSON(20,25,FALSE)": "0.0519174686084913",
"=POISSON(35,40,TRUE)": "0.242414197690103",
+ // SUBTOTAL
+ "=SUBTOTAL(1,A1:A6)": "1.5",
+ "=SUBTOTAL(2,A1:A6)": "4",
+ "=SUBTOTAL(3,A1:A6)": "4",
+ "=SUBTOTAL(4,A1:A6)": "3",
+ "=SUBTOTAL(5,A1:A6)": "0",
+ "=SUBTOTAL(6,A1:A6)": "0",
+ "=SUBTOTAL(7,A1:A6)": "1.29099444873581",
+ "=SUBTOTAL(8,A1:A6)": "1.11803398874989",
+ "=SUBTOTAL(9,A1:A6)": "6",
+ "=SUBTOTAL(10,A1:A6)": "1.66666666666667",
+ "=SUBTOTAL(11,A1:A6)": "1.25",
+ "=SUBTOTAL(101,A1:A6)": "1.5",
+ "=SUBTOTAL(102,A1:A6)": "4",
+ "=SUBTOTAL(103,A1:A6)": "4",
+ "=SUBTOTAL(104,A1:A6)": "3",
+ "=SUBTOTAL(105,A1:A6)": "0",
+ "=SUBTOTAL(106,A1:A6)": "0",
+ "=SUBTOTAL(107,A1:A6)": "1.29099444873581",
+ "=SUBTOTAL(108,A1:A6)": "1.11803398874989",
+ "=SUBTOTAL(109,A1:A6)": "6",
+ "=SUBTOTAL(109,A1:A6,A1:A6)": "12",
+ "=SUBTOTAL(110,A1:A6)": "1.66666666666667",
+ "=SUBTOTAL(111,A1:A6)": "1.25",
+ "=SUBTOTAL(111,A1:A6,A1:A6)": "1.25",
// SUM
"=SUM(1,2)": "3",
`=SUM("",1,2)`: "3",
@@ -2344,6 +2387,15 @@ func TestCalcCellValue(t *testing.T) {
"=_xlfn.ACOTH()": "ACOTH requires 1 numeric argument",
`=_xlfn.ACOTH("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
"=_xlfn.ACOTH(_xlfn.ACOTH(2))": "#NUM!",
+ // _xlfn.AGGREGATE
+ "=_xlfn.AGGREGATE()": "AGGREGATE requires at least 3 arguments",
+ "=_xlfn.AGGREGATE(\"\",0,A4:A5)": "strconv.ParseFloat: parsing \"\": invalid syntax",
+ "=_xlfn.AGGREGATE(1,\"\",A4:A5)": "strconv.ParseFloat: parsing \"\": invalid syntax",
+ "=_xlfn.AGGREGATE(0,A4:A5)": "AGGREGATE has invalid function_num",
+ "=_xlfn.AGGREGATE(1,8,A4:A5)": "AGGREGATE has invalid options",
+ "=_xlfn.AGGREGATE(1,0,A5:A6)": "#DIV/0!",
+ "=_xlfn.AGGREGATE(13,0,A1:A6)": "#N/A",
+ "=_xlfn.AGGREGATE(18,0,A1:A6,1)": "#NUM!",
// _xlfn.ARABIC
"=_xlfn.ARABIC()": "ARABIC requires 1 numeric argument",
"=_xlfn.ARABIC(\"" + strings.Repeat("I", 256) + "\")": "#VALUE!",
@@ -2611,6 +2663,11 @@ func TestCalcCellValue(t *testing.T) {
"=POISSON(0,\"\",FALSE)": "strconv.ParseFloat: parsing \"\": invalid syntax",
"=POISSON(0,0,\"\")": "strconv.ParseBool: parsing \"\": invalid syntax",
"=POISSON(0,-1,TRUE)": "#N/A",
+ // SUBTOTAL
+ "=SUBTOTAL()": "SUBTOTAL requires at least 2 arguments",
+ "=SUBTOTAL(\"\",A4:A5)": "strconv.ParseFloat: parsing \"\": invalid syntax",
+ "=SUBTOTAL(0,A4:A5)": "SUBTOTAL has invalid function_num",
+ "=SUBTOTAL(1,A5:A6)": "#DIV/0!",
// SUM
"=SUM((": ErrInvalidFormula.Error(),
"=SUM(-)": ErrInvalidFormula.Error(),
diff --git a/cell.go b/cell.go
index 550ca38..3fcbb7b 100644
--- a/cell.go
+++ b/cell.go
@@ -826,6 +826,7 @@ func getCellRichText(si *xlsxSI) (runs []RichTextRun) {
if v.RPr.Color.Theme != nil {
font.ColorTheme = v.RPr.Color.Theme
}
+ font.ColorIndexed = v.RPr.Color.Indexed
font.ColorTint = v.RPr.Color.Tint
}
run.Font = &font
diff --git a/cell_test.go b/cell_test.go
index 511078e..980058a 100644
--- a/cell_test.go
+++ b/cell_test.go
@@ -298,42 +298,46 @@ func TestGetCellValue(t *testing.T) {
assert.NoError(t, err)
f.Sheet.Delete("xl/worksheets/sheet1.xml")
- f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `
- 2422.3000000000002
- 2422.3000000000002
- 12.4
- 964
- 1101.5999999999999
- 275.39999999999998
- 68.900000000000006
- 44385.208333333336
- 5.0999999999999996
- 5.1100000000000003
- 5.0999999999999996
- 5.1109999999999998
- 5.1111000000000004
- 2422.012345678
- 2422.0123456789
- 12.012345678901
- 964
- 1101.5999999999999
- 275.39999999999998
- 68.900000000000006
- 8.8880000000000001E-2
- 4.0000000000000003e-5
- 2422.3000000000002
- 1101.5999999999999
- 275.39999999999998
- 68.900000000000006
- 1.1000000000000001
- 1234567890123_4
- 123456789_0123_4
- +0.0000000000000000002399999999999992E-4
- 7.2399999999999992E-2
-
`)))
+ f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `
+ 2422.3000000000002
+ 2422.3000000000002
+ 12.4
+ 964
+ 1101.5999999999999
+ 275.39999999999998
+ 68.900000000000006
+ 44385.208333333336
+ 5.0999999999999996
+ 5.1100000000000003
+ 5.0999999999999996
+ 5.1109999999999998
+ 5.1111000000000004
+ 2422.012345678
+ 2422.0123456789
+ 12.012345678901
+ 964
+ 1101.5999999999999
+ 275.39999999999998
+ 68.900000000000006
+ 8.8880000000000001E-2
+ 4.0000000000000003e-5
+ 2422.3000000000002
+ 1101.5999999999999
+ 275.39999999999998
+ 68.900000000000006
+ 1.1000000000000001
+ 1234567890123_4
+ 123456789_0123_4
+ +0.0000000000000000002399999999999992E-4
+ 7.2399999999999992E-2
+ 20200208T080910.123
+ 20200208T080910,123
+ 20221022T150529Z
+ 2022-10-22T15:05:29Z
+ 2020-07-10 15:00:00.000
`)))
f.checked = nil
- rows, err = f.GetRows("Sheet1")
- assert.Equal(t, [][]string{{
+ rows, err = f.GetCols("Sheet1")
+ assert.Equal(t, []string{
"2422.3",
"2422.3",
"12.4",
@@ -365,7 +369,12 @@ func TestGetCellValue(t *testing.T) {
"123456789_0123_4",
"2.39999999999999E-23",
"0.0724",
- }}, rows)
+ "43869.3397004977",
+ "43869.3397004977",
+ "44856.6288078704",
+ "44856.6288078704",
+ "2020-07-10 15:00:00.000",
+ }, rows[0])
assert.NoError(t, err)
}
@@ -596,9 +605,10 @@ func TestSetCellRichText(t *testing.T) {
{
Text: "bold",
Font: &Font{
- Bold: true,
- Color: "2354e8",
- Family: "Times New Roman",
+ Bold: true,
+ Color: "2354e8",
+ ColorIndexed: 0,
+ Family: "Times New Roman",
},
},
{
@@ -742,7 +752,7 @@ func TestSharedStringsError(t *testing.T) {
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.
+ // Test reload the file error on set cell value and rich text. The error message was different between macOS and Windows.
err = f.SetCellValue("Sheet1", "A19", "A19")
assert.Error(t, err)
diff --git a/file.go b/file.go
index 7ce536c..43a37dd 100644
--- a/file.go
+++ b/file.go
@@ -176,7 +176,7 @@ func (f *File) writeToZip(zw *zip.Writer) error {
f.workBookWriter()
f.workSheetWriter()
f.relsWriter()
- f.sharedStringsLoader()
+ _ = f.sharedStringsLoader()
f.sharedStringsWriter()
f.styleSheetWriter()
diff --git a/rows.go b/rows.go
index 1fd6825..9f791cb 100644
--- a/rows.go
+++ b/rows.go
@@ -20,6 +20,8 @@ import (
"math"
"os"
"strconv"
+ "strings"
+ "time"
"github.com/mohae/deepcopy"
)
@@ -447,6 +449,39 @@ func (f *File) sharedStringsReader() *xlsxSST {
return f.SharedStrings
}
+// getCellDate parse cell value which containing a boolean.
+func (c *xlsxC) getCellBool(f *File, raw bool) (string, error) {
+ if !raw {
+ if c.V == "1" {
+ return "TRUE", nil
+ }
+ if c.V == "0" {
+ return "FALSE", nil
+ }
+ }
+ return f.formattedValue(c.S, c.V, raw), nil
+}
+
+// getCellDate parse cell value which contains a date in the ISO 8601 format.
+func (c *xlsxC) getCellDate(f *File, raw bool) (string, error) {
+ if !raw {
+ layout := "20060102T150405.999"
+ if strings.HasSuffix(c.V, "Z") {
+ layout = "20060102T150405Z"
+ if strings.Contains(c.V, "-") {
+ layout = "2006-01-02T15:04:05Z"
+ }
+ } else if strings.Contains(c.V, "-") {
+ layout = "2006-01-02 15:04:05Z"
+ }
+ if timestamp, err := time.Parse(layout, strings.ReplaceAll(c.V, ",", ".")); err == nil {
+ excelTime, _ := timeToExcelTime(timestamp, false)
+ c.V = strconv.FormatFloat(excelTime, 'G', 15, 64)
+ }
+ }
+ return f.formattedValue(c.S, c.V, raw), nil
+}
+
// getValueFrom return a value from a column/row cell, this function is
// intended to be used with for range on rows an argument with the spreadsheet
// opened file.
@@ -455,15 +490,9 @@ func (c *xlsxC) getValueFrom(f *File, d *xlsxSST, raw bool) (string, error) {
defer f.Unlock()
switch c.T {
case "b":
- if !raw {
- if c.V == "1" {
- return "TRUE", nil
- }
- if c.V == "0" {
- return "FALSE", nil
- }
- }
- return f.formattedValue(c.S, c.V, raw), nil
+ return c.getCellBool(f, raw)
+ case "d":
+ return c.getCellDate(f, raw)
case "s":
if c.V != "" {
xlsxSI := 0
@@ -760,7 +789,7 @@ func (f *File) duplicateMergeCells(sheet string, ws *xlsxWorksheet, row, row2 in
//
//
//
-// Noteice: this method could be very slow for large spreadsheets (more than
+// Notice: this method could be very slow for large spreadsheets (more than
// 3000 rows one sheet).
func checkRow(ws *xlsxWorksheet) error {
for rowIdx := range ws.SheetData.Row {
@@ -793,7 +822,7 @@ func checkRow(ws *xlsxWorksheet) error {
if colCount < lastCol {
oldList := rowData.C
- newlist := make([]xlsxC, 0, lastCol)
+ newList := make([]xlsxC, 0, lastCol)
rowData.C = ws.SheetData.Row[rowIdx].C[:0]
@@ -802,10 +831,10 @@ func checkRow(ws *xlsxWorksheet) error {
if err != nil {
return err
}
- newlist = append(newlist, xlsxC{R: cellName})
+ newList = append(newList, xlsxC{R: cellName})
}
- rowData.C = newlist
+ rowData.C = newList
for colIdx := range oldList {
colData := &oldList[colIdx]
diff --git a/rows_test.go b/rows_test.go
index 76823ba..423932f 100644
--- a/rows_test.go
+++ b/rows_test.go
@@ -1048,7 +1048,7 @@ func TestNumberFormats(t *testing.T) {
{"A32", numFmt40, -8.8888666665555487, "(8.89)"},
} {
cell, styleID, value, expected := cases[0].(string), cases[1].(int), cases[2], cases[3].(string)
- f.SetCellStyle("Sheet1", cell, cell, styleID)
+ assert.NoError(t, f.SetCellStyle("Sheet1", cell, cell, styleID))
assert.NoError(t, f.SetCellValue("Sheet1", cell, value))
result, err := f.GetCellValue("Sheet1", cell)
assert.NoError(t, err)
diff --git a/sheetpr.go b/sheetpr.go
index 3a805c4..b0f3945 100644
--- a/sheetpr.go
+++ b/sheetpr.go
@@ -80,24 +80,12 @@ func (f *File) GetPageMargins(sheet string) (PageLayoutMarginsOptions, error) {
return opts, err
}
if ws.PageMargins != nil {
- if ws.PageMargins.Bottom != 0 {
- opts.Bottom = float64Ptr(ws.PageMargins.Bottom)
- }
- if ws.PageMargins.Footer != 0 {
- opts.Footer = float64Ptr(ws.PageMargins.Footer)
- }
- if ws.PageMargins.Header != 0 {
- opts.Header = float64Ptr(ws.PageMargins.Header)
- }
- if ws.PageMargins.Left != 0 {
- opts.Left = float64Ptr(ws.PageMargins.Left)
- }
- if ws.PageMargins.Right != 0 {
- opts.Right = float64Ptr(ws.PageMargins.Right)
- }
- if ws.PageMargins.Top != 0 {
- opts.Top = float64Ptr(ws.PageMargins.Top)
- }
+ opts.Bottom = float64Ptr(ws.PageMargins.Bottom)
+ opts.Footer = float64Ptr(ws.PageMargins.Footer)
+ opts.Header = float64Ptr(ws.PageMargins.Header)
+ opts.Left = float64Ptr(ws.PageMargins.Left)
+ opts.Right = float64Ptr(ws.PageMargins.Right)
+ opts.Top = float64Ptr(ws.PageMargins.Top)
}
if ws.PrintOptions != nil {
opts.Horizontally = boolPtr(ws.PrintOptions.HorizontalCentered)
@@ -106,7 +94,7 @@ func (f *File) GetPageMargins(sheet string) (PageLayoutMarginsOptions, error) {
return opts, err
}
-// prepareSheetPr sheetPr element if which not exist.
+// prepareSheetPr create sheetPr element which not exist.
func (ws *xlsxWorksheet) prepareSheetPr() {
if ws.SheetPr == nil {
ws.SheetPr = new(xlsxSheetPr)
diff --git a/sheetpr_test.go b/sheetpr_test.go
index b4ee18d..d422e3f 100644
--- a/sheetpr_test.go
+++ b/sheetpr_test.go
@@ -1,7 +1,6 @@
package excelize
import (
- "path/filepath"
"testing"
"github.com/stretchr/testify/assert"
@@ -39,21 +38,6 @@ func TestGetPageMargins(t *testing.T) {
assert.EqualError(t, err, "sheet SheetN does not exist")
}
-func TestDebug(t *testing.T) {
- f := NewFile()
- assert.NoError(t, f.SetSheetProps("Sheet1", nil))
- ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
- assert.True(t, ok)
- ws.(*xlsxWorksheet).PageMargins = nil
- ws.(*xlsxWorksheet).PrintOptions = nil
- ws.(*xlsxWorksheet).SheetPr = nil
- ws.(*xlsxWorksheet).SheetFormatPr = nil
- // w := uint8(10)
- // f.SetSheetProps("Sheet1", &SheetPropsOptions{BaseColWidth: &w})
- f.SetPageMargins("Sheet1", &PageLayoutMarginsOptions{Horizontally: boolPtr(true)})
- assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDebug.xlsx")))
-}
-
func TestSetSheetProps(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetSheetProps("Sheet1", nil))
diff --git a/stream.go b/stream.go
index 44d8eb7..aaa4589 100644
--- a/stream.go
+++ b/stream.go
@@ -369,11 +369,11 @@ func (sw *StreamWriter) SetRow(cell string, values []interface{}, opts ...RowOpt
if err != nil {
return err
}
- sw.rawData.WriteString(``)
+ _, _ = sw.rawData.WriteString(``)
for i, val := range values {
if val == nil {
continue
@@ -643,12 +643,12 @@ type bufferedWriter struct {
buf bytes.Buffer
}
-// Write to the in-memory buffer. The err is always nil.
+// Write to the in-memory buffer. The error is always nil.
func (bw *bufferedWriter) Write(p []byte) (n int, err error) {
return bw.buf.Write(p)
}
-// WriteString wite to the in-memory buffer. The err is always nil.
+// WriteString write to the in-memory buffer. The error is always nil.
func (bw *bufferedWriter) WriteString(p string) (n int, err error) {
return bw.buf.WriteString(p)
}
diff --git a/stream_test.go b/stream_test.go
index a4a0590..4e83626 100644
--- a/stream_test.go
+++ b/stream_test.go
@@ -235,7 +235,7 @@ func TestStreamSetRowNilValues(t *testing.T) {
file := NewFile()
streamWriter, err := file.NewStreamWriter("Sheet1")
assert.NoError(t, err)
- streamWriter.SetRow("A1", []interface{}{nil, nil, Cell{Value: "foo"}})
+ assert.NoError(t, streamWriter.SetRow("A1", []interface{}{nil, nil, Cell{Value: "foo"}}))
streamWriter.Flush()
ws, err := file.workSheetReader("Sheet1")
assert.NoError(t, err)
diff --git a/styles.go b/styles.go
index bbb21dc..15de5f1 100644
--- a/styles.go
+++ b/styles.go
@@ -2164,6 +2164,10 @@ func newFontColor(font *Font) *xlsxColor {
prepareFontColor()
fontColor.RGB = getPaletteColor(font.Color)
}
+ if font.ColorIndexed >= 0 && font.ColorIndexed <= len(IndexedColorMapping)+1 {
+ prepareFontColor()
+ fontColor.Indexed = font.ColorIndexed
+ }
if font.ColorTheme != nil {
prepareFontColor()
fontColor.Theme = font.ColorTheme
diff --git a/table.go b/table.go
index 112882c..867af9e 100644
--- a/table.go
+++ b/table.go
@@ -221,7 +221,7 @@ func parseAutoFilterOptions(opts string) (*autoFilterOptions, error) {
//
// err := f.AutoFilter("Sheet1", "A1", "D4", `{"column":"B","expression":"x != blanks"}`)
//
-// column defines the filter columns in a auto filter range based on simple
+// column defines the filter columns in an auto filter range based on simple
// criteria
//
// It isn't sufficient to just specify the filter condition. You must also
diff --git a/xmlDrawing.go b/xmlDrawing.go
index b52e449..5b4628b 100644
--- a/xmlDrawing.go
+++ b/xmlDrawing.go
@@ -141,6 +141,26 @@ const (
ColorMappingTypeUnset int = -1
)
+// IndexedColorMapping is the table of default mappings from indexed color value
+// to RGB value. Note that 0-7 are redundant of 8-15 to preserve backwards
+// compatibility. A legacy indexing scheme for colors that is still required
+// for some records, and for backwards compatibility with legacy formats. This
+// element contains a sequence of RGB color values that correspond to color
+// indexes (zero-based). When using the default indexed color palette, the
+// values are not written out, but instead are implied. When the color palette
+// has been modified from default, then the entire color palette is written
+// out.
+var IndexedColorMapping = []string{
+ "000000", "FFFFFF", "FF0000", "00FF00", "0000FF", "FFFF00", "FF00FF", "00FFFF",
+ "000000", "FFFFFF", "FF0000", "00FF00", "0000FF", "FFFF00", "FF00FF", "00FFFF",
+ "800000", "008000", "000080", "808000", "800080", "008080", "C0C0C0", "808080",
+ "9999FF", "993366", "FFFFCC", "CCFFFF", "660066", "FF8080", "0066CC", "CCCCFF",
+ "000080", "FF00FF", "FFFF00", "00FFFF", "800080", "800000", "008080", "0000FF",
+ "00CCFF", "CCFFFF", "CCFFCC", "FFFF99", "99CCFF", "FF99CC", "CC99FF", "FFCC99",
+ "3366FF", "33CCCC", "99CC00", "FFCC00", "FF9900", "FF6600", "666699", "969696",
+ "003366", "339966", "003300", "333300", "993300", "993366", "333399", "333333",
+}
+
// supportedImageTypes defined supported image types.
var supportedImageTypes = map[string]string{".gif": ".gif", ".jpg": ".jpeg", ".jpeg": ".jpeg", ".png": ".png", ".tif": ".tiff", ".tiff": ".tiff", ".emf": ".emf", ".wmf": ".wmf", ".emz": ".emz", ".wmz": ".wmz"}
diff --git a/xmlStyles.go b/xmlStyles.go
index e35dbdd..c9e0761 100644
--- a/xmlStyles.go
+++ b/xmlStyles.go
@@ -334,16 +334,17 @@ type Border struct {
// Font directly maps the font settings of the fonts.
type Font struct {
- Bold bool `json:"bold"`
- Italic bool `json:"italic"`
- Underline string `json:"underline"`
- Family string `json:"family"`
- Size float64 `json:"size"`
- Strike bool `json:"strike"`
- Color string `json:"color"`
- ColorTheme *int `json:"color_theme"`
- ColorTint float64 `json:"color_tint"`
- VertAlign string `json:"vertAlign"`
+ Bold bool `json:"bold"`
+ Italic bool `json:"italic"`
+ Underline string `json:"underline"`
+ Family string `json:"family"`
+ Size float64 `json:"size"`
+ Strike bool `json:"strike"`
+ Color string `json:"color"`
+ ColorIndexed int `json:"color_indexed"`
+ ColorTheme *int `json:"color_theme"`
+ ColorTint float64 `json:"color_tint"`
+ VertAlign string `json:"vertAlign"`
}
// Fill directly maps the fill settings of the cells.