From 426ecf04ac844ccd5f19e61ffd6dc8d555e8e88d Mon Sep 17 00:00:00 2001 From: xuri Date: Mon, 24 Oct 2022 00:02:22 +0800 Subject: [PATCH] Support get cell value which contains a date in the ISO 8601 format - Support set and get font color with indexed color - New export variable `IndexedColorMapping` - Fix getting incorrect page margin settings when the margin is 0 - Update unit tests and comments typo fixes - ref #65, new formula functions: AGGREGATE and SUBTOTAL --- calc.go | 94 ++++++++++++++++++++++++++++++++++++++++++++++--- calc_test.go | 61 ++++++++++++++++++++++++++++++-- cell.go | 1 + cell_test.go | 90 +++++++++++++++++++++++++--------------------- file.go | 2 +- rows.go | 55 ++++++++++++++++++++++------- rows_test.go | 2 +- sheetpr.go | 26 ++++---------- sheetpr_test.go | 16 --------- stream.go | 14 ++++---- stream_test.go | 2 +- styles.go | 4 +++ table.go | 2 +- xmlDrawing.go | 20 +++++++++++ xmlStyles.go | 21 +++++------ 15 files changed, 294 insertions(+), 116 deletions(-) diff --git a/calc.go b/calc.go index 796ca169fd..c600aaa32b 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 ea3f014806..1a8b8c62ec 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 550ca3879c..3fcbb7b3a4 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 511078ee54..980058a30e 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 7ce536c86e..43a37dd294 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 1fd6825ba9..9f791cb80b 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 76823bad65..423932f8ac 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 3a805c479a..b0f3945121 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 b4ee18dba2..d422e3f65b 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 44d8eb710f..aaa45893f8 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 a4a0590aae..4e83626bcf 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 bbb21dcc50..15de5f1ab1 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 112882c235..867af9e24e 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 b52e44970b..5b4628b5e2 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 e35dbddc93..c9e0761f73 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.