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
pull/2/head
xuri 2 years ago
parent f843a9ea56
commit 14c6a198ce
No known key found for this signature in database
GPG Key ID: BA5E5BB1C948EDF7

@ -339,6 +339,7 @@ type formulaFuncs struct {
// ACOT // ACOT
// ACOTH // ACOTH
// ADDRESS // ADDRESS
// AGGREGATE
// AMORDEGRC // AMORDEGRC
// AMORLINC // AMORLINC
// AND // AND
@ -700,6 +701,7 @@ type formulaFuncs struct {
// STDEVPA // STDEVPA
// STEYX // STEYX
// SUBSTITUTE // SUBSTITUTE
// SUBTOTAL
// SUM // SUM
// SUMIF // SUMIF
// SUMIFS // SUMIFS
@ -872,7 +874,6 @@ func (f *File) evalInfixExp(ctx *calcContext, sheet, cell string, tokens []efp.T
var err error var err error
opdStack, optStack, opfStack, opfdStack, opftStack, argsStack := NewStack(), NewStack(), NewStack(), NewStack(), NewStack(), NewStack() opdStack, optStack, opfStack, opfdStack, opftStack, argsStack := NewStack(), NewStack(), NewStack(), NewStack(), NewStack(), NewStack()
var inArray, inArrayRow bool var inArray, inArrayRow bool
var arrayRow []formulaArg
for i := 0; i < len(tokens); i++ { for i := 0; i < len(tokens); i++ {
token := 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)) argsStack.Peek().(*list.List).PushBack(newStringFormulaArg(token.TValue))
} }
if inArrayRow && isOperand(token) { if inArrayRow && isOperand(token) {
arrayRow = append(arrayRow, tokenToFormulaArg(token))
continue continue
} }
if inArrayRow && isFunctionStopToken(token) { if inArrayRow && isFunctionStopToken(token) {
@ -990,7 +990,7 @@ func (f *File) evalInfixExp(ctx *calcContext, sheet, cell string, tokens []efp.T
} }
if inArray && isFunctionStopToken(token) { if inArray && isFunctionStopToken(token) {
argsStack.Peek().(*list.List).PushBack(opfdStack.Pop()) argsStack.Peek().(*list.List).PushBack(opfdStack.Pop())
arrayRow, inArray = []formulaArg{}, false inArray = false
continue continue
} }
if err = f.evalInfixExpFunc(ctx, sheet, cell, token, nextToken, opfStack, opdStack, opftStack, opfdStack, argsStack); err != nil { 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)) 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 // ARABIC function converts a Roman numeral into an Arabic numeral. The syntax
// of the function is: // 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)) 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 // SUM function adds together a supplied set of numbers and returns the sum of
// these values. The syntax of the function is: // these values. The syntax of the function is:
// //
@ -11622,8 +11707,7 @@ func (fn *formulaFuncs) OR(argsList *list.List) formulaArg {
} }
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
case ArgNumber: case ArgNumber:
or = token.Number != 0 if or = token.Number != 0; or {
if or {
return newStringFormulaArg(strings.ToUpper(strconv.FormatBool(or))) return newStringFormulaArg(strings.ToUpper(strconv.FormatBool(or)))
} }
case ArgMatrix: case ArgMatrix:

@ -393,16 +393,34 @@ func TestCalcCellValue(t *testing.T) {
"=ACOSH(2.5)": "1.56679923697241", "=ACOSH(2.5)": "1.56679923697241",
"=ACOSH(5)": "2.29243166956118", "=ACOSH(5)": "2.29243166956118",
"=ACOSH(ACOSH(5))": "1.47138332153668", "=ACOSH(ACOSH(5))": "1.47138332153668",
// ACOT // _xlfn.ACOT
"=_xlfn.ACOT(1)": "0.785398163397448", "=_xlfn.ACOT(1)": "0.785398163397448",
"=_xlfn.ACOT(-2)": "2.67794504458899", "=_xlfn.ACOT(-2)": "2.67794504458899",
"=_xlfn.ACOT(0)": "1.5707963267949", "=_xlfn.ACOT(0)": "1.5707963267949",
"=_xlfn.ACOT(_xlfn.ACOT(0))": "0.566911504941009", "=_xlfn.ACOT(_xlfn.ACOT(0))": "0.566911504941009",
// ACOTH // _xlfn.ACOTH
"=_xlfn.ACOTH(-5)": "-0.202732554054082", "=_xlfn.ACOTH(-5)": "-0.202732554054082",
"=_xlfn.ACOTH(1.1)": "1.52226121886171", "=_xlfn.ACOTH(1.1)": "1.52226121886171",
"=_xlfn.ACOTH(2)": "0.549306144334055", "=_xlfn.ACOTH(2)": "0.549306144334055",
"=_xlfn.ACOTH(ABS(-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 // ARABIC
"=_xlfn.ARABIC(\"IV\")": "4", "=_xlfn.ARABIC(\"IV\")": "4",
"=_xlfn.ARABIC(\"-IV\")": "-4", "=_xlfn.ARABIC(\"-IV\")": "-4",
@ -791,6 +809,31 @@ func TestCalcCellValue(t *testing.T) {
// POISSON // POISSON
"=POISSON(20,25,FALSE)": "0.0519174686084913", "=POISSON(20,25,FALSE)": "0.0519174686084913",
"=POISSON(35,40,TRUE)": "0.242414197690103", "=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
"=SUM(1,2)": "3", "=SUM(1,2)": "3",
`=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()": "ACOTH requires 1 numeric argument",
`=_xlfn.ACOTH("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax", `=_xlfn.ACOTH("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
"=_xlfn.ACOTH(_xlfn.ACOTH(2))": "#NUM!", "=_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
"=_xlfn.ARABIC()": "ARABIC requires 1 numeric argument", "=_xlfn.ARABIC()": "ARABIC requires 1 numeric argument",
"=_xlfn.ARABIC(\"" + strings.Repeat("I", 256) + "\")": "#VALUE!", "=_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,\"\",FALSE)": "strconv.ParseFloat: parsing \"\": invalid syntax",
"=POISSON(0,0,\"\")": "strconv.ParseBool: parsing \"\": invalid syntax", "=POISSON(0,0,\"\")": "strconv.ParseBool: parsing \"\": invalid syntax",
"=POISSON(0,-1,TRUE)": "#N/A", "=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
"=SUM((": ErrInvalidFormula.Error(), "=SUM((": ErrInvalidFormula.Error(),
"=SUM(-)": ErrInvalidFormula.Error(), "=SUM(-)": ErrInvalidFormula.Error(),

@ -826,6 +826,7 @@ func getCellRichText(si *xlsxSI) (runs []RichTextRun) {
if v.RPr.Color.Theme != nil { if v.RPr.Color.Theme != nil {
font.ColorTheme = v.RPr.Color.Theme font.ColorTheme = v.RPr.Color.Theme
} }
font.ColorIndexed = v.RPr.Color.Indexed
font.ColorTint = v.RPr.Color.Tint font.ColorTint = v.RPr.Color.Tint
} }
run.Font = &font run.Font = &font

@ -298,42 +298,46 @@ func TestGetCellValue(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
f.Sheet.Delete("xl/worksheets/sheet1.xml") f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `<row r="1"> f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `
<c r="A1"><v>2422.3000000000002</v></c> <row r="1"><c r="A1"><v>2422.3000000000002</v></c></row>
<c r="B1"><v>2422.3000000000002</v></c> <row r="2"><c r="A2"><v>2422.3000000000002</v></c></row>
<c r="C1"><v>12.4</v></c> <row r="3"><c r="A3"><v>12.4</v></c></row>
<c r="D1"><v>964</v></c> <row r="4"><c r="A4"><v>964</v></c></row>
<c r="E1"><v>1101.5999999999999</v></c> <row r="5"><c r="A5"><v>1101.5999999999999</v></c></row>
<c r="F1"><v>275.39999999999998</v></c> <row r="6"><c r="A6"><v>275.39999999999998</v></c></row>
<c r="G1"><v>68.900000000000006</v></c> <row r="7"><c r="A7"><v>68.900000000000006</v></c></row>
<c r="H1"><v>44385.208333333336</v></c> <row r="8"><c r="A8"><v>44385.208333333336</v></c></row>
<c r="I1"><v>5.0999999999999996</v></c> <row r="9"><c r="A9"><v>5.0999999999999996</v></c></row>
<c r="J1"><v>5.1100000000000003</v></c> <row r="10"><c r="A10"><v>5.1100000000000003</v></c></row>
<c r="K1"><v>5.0999999999999996</v></c> <row r="11"><c r="A11"><v>5.0999999999999996</v></c></row>
<c r="L1"><v>5.1109999999999998</v></c> <row r="12"><c r="A12"><v>5.1109999999999998</v></c></row>
<c r="M1"><v>5.1111000000000004</v></c> <row r="13"><c r="A13"><v>5.1111000000000004</v></c></row>
<c r="N1"><v>2422.012345678</v></c> <row r="14"><c r="A14"><v>2422.012345678</v></c></row>
<c r="O1"><v>2422.0123456789</v></c> <row r="15"><c r="A15"><v>2422.0123456789</v></c></row>
<c r="P1"><v>12.012345678901</v></c> <row r="16"><c r="A16"><v>12.012345678901</v></c></row>
<c r="Q1"><v>964</v></c> <row r="17"><c r="A17"><v>964</v></c></row>
<c r="R1"><v>1101.5999999999999</v></c> <row r="18"><c r="A18"><v>1101.5999999999999</v></c></row>
<c r="S1"><v>275.39999999999998</v></c> <row r="19"><c r="A19"><v>275.39999999999998</v></c></row>
<c r="T1"><v>68.900000000000006</v></c> <row r="20"><c r="A20"><v>68.900000000000006</v></c></row>
<c r="U1"><v>8.8880000000000001E-2</v></c> <row r="21"><c r="A21"><v>8.8880000000000001E-2</v></c></row>
<c r="V1"><v>4.0000000000000003e-5</v></c> <row r="22"><c r="A22"><v>4.0000000000000003e-5</v></c></row>
<c r="W1"><v>2422.3000000000002</v></c> <row r="23"><c r="A23"><v>2422.3000000000002</v></c></row>
<c r="X1"><v>1101.5999999999999</v></c> <row r="24"><c r="A24"><v>1101.5999999999999</v></c></row>
<c r="Y1"><v>275.39999999999998</v></c> <row r="25"><c r="A25"><v>275.39999999999998</v></c></row>
<c r="Z1"><v>68.900000000000006</v></c> <row r="26"><c r="A26"><v>68.900000000000006</v></c></row>
<c r="AA1"><v>1.1000000000000001</v></c> <row r="27"><c r="A27"><v>1.1000000000000001</v></c></row>
<c r="AB1" t="str"><v>1234567890123_4</v></c> <row r="28"><c r="A28" t="str"><v>1234567890123_4</v></c></row>
<c r="AC1" t="str"><v>123456789_0123_4</v></c> <row r="29"><c r="A29" t="str"><v>123456789_0123_4</v></c></row>
<c r="AD1"><v>+0.0000000000000000002399999999999992E-4</v></c> <row r="30"><c r="A30"><v>+0.0000000000000000002399999999999992E-4</v></c></row>
<c r="AE1"><v>7.2399999999999992E-2</v></c> <row r="31"><c r="A31"><v>7.2399999999999992E-2</v></c></row>
</row>`))) <row r="32"><c r="A32" t="d"><v>20200208T080910.123</v></c></row>
<row r="33"><c r="A33" t="d"><v>20200208T080910,123</v></c></row>
<row r="34"><c r="A34" t="d"><v>20221022T150529Z</v></c></row>
<row r="35"><c r="A35" t="d"><v>2022-10-22T15:05:29Z</v></c></row>
<row r="36"><c r="A36" t="d"><v>2020-07-10 15:00:00.000</v></c></row>`)))
f.checked = nil f.checked = nil
rows, err = f.GetRows("Sheet1") rows, err = f.GetCols("Sheet1")
assert.Equal(t, [][]string{{ assert.Equal(t, []string{
"2422.3", "2422.3",
"2422.3", "2422.3",
"12.4", "12.4",
@ -365,7 +369,12 @@ func TestGetCellValue(t *testing.T) {
"123456789_0123_4", "123456789_0123_4",
"2.39999999999999E-23", "2.39999999999999E-23",
"0.0724", "0.0724",
}}, rows) "43869.3397004977",
"43869.3397004977",
"44856.6288078704",
"44856.6288078704",
"2020-07-10 15:00:00.000",
}, rows[0])
assert.NoError(t, err) assert.NoError(t, err)
} }
@ -596,9 +605,10 @@ func TestSetCellRichText(t *testing.T) {
{ {
Text: "bold", Text: "bold",
Font: &Font{ Font: &Font{
Bold: true, Bold: true,
Color: "2354e8", Color: "2354e8",
Family: "Times New Roman", ColorIndexed: 0,
Family: "Times New Roman",
}, },
}, },
{ {
@ -742,7 +752,7 @@ func TestSharedStringsError(t *testing.T) {
assert.Equal(t, "1", f.getFromStringItem(1)) assert.Equal(t, "1", f.getFromStringItem(1))
// Cleanup undelete temporary files // Cleanup undelete temporary files
assert.NoError(t, os.Remove(tempFile.(string))) 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") err = f.SetCellValue("Sheet1", "A19", "A19")
assert.Error(t, err) assert.Error(t, err)

@ -176,7 +176,7 @@ func (f *File) writeToZip(zw *zip.Writer) error {
f.workBookWriter() f.workBookWriter()
f.workSheetWriter() f.workSheetWriter()
f.relsWriter() f.relsWriter()
f.sharedStringsLoader() _ = f.sharedStringsLoader()
f.sharedStringsWriter() f.sharedStringsWriter()
f.styleSheetWriter() f.styleSheetWriter()

@ -20,6 +20,8 @@ import (
"math" "math"
"os" "os"
"strconv" "strconv"
"strings"
"time"
"github.com/mohae/deepcopy" "github.com/mohae/deepcopy"
) )
@ -447,6 +449,39 @@ func (f *File) sharedStringsReader() *xlsxSST {
return f.SharedStrings 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 // 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 // intended to be used with for range on rows an argument with the spreadsheet
// opened file. // opened file.
@ -455,15 +490,9 @@ func (c *xlsxC) getValueFrom(f *File, d *xlsxSST, raw bool) (string, error) {
defer f.Unlock() defer f.Unlock()
switch c.T { switch c.T {
case "b": case "b":
if !raw { return c.getCellBool(f, raw)
if c.V == "1" { case "d":
return "TRUE", nil return c.getCellDate(f, raw)
}
if c.V == "0" {
return "FALSE", nil
}
}
return f.formattedValue(c.S, c.V, raw), nil
case "s": case "s":
if c.V != "" { if c.V != "" {
xlsxSI := 0 xlsxSI := 0
@ -760,7 +789,7 @@ func (f *File) duplicateMergeCells(sheet string, ws *xlsxWorksheet, row, row2 in
// <c r="G15" s="1" /> // <c r="G15" s="1" />
// </row> // </row>
// //
// 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). // 3000 rows one sheet).
func checkRow(ws *xlsxWorksheet) error { func checkRow(ws *xlsxWorksheet) error {
for rowIdx := range ws.SheetData.Row { for rowIdx := range ws.SheetData.Row {
@ -793,7 +822,7 @@ func checkRow(ws *xlsxWorksheet) error {
if colCount < lastCol { if colCount < lastCol {
oldList := rowData.C oldList := rowData.C
newlist := make([]xlsxC, 0, lastCol) newList := make([]xlsxC, 0, lastCol)
rowData.C = ws.SheetData.Row[rowIdx].C[:0] rowData.C = ws.SheetData.Row[rowIdx].C[:0]
@ -802,10 +831,10 @@ func checkRow(ws *xlsxWorksheet) error {
if err != nil { if err != nil {
return err return err
} }
newlist = append(newlist, xlsxC{R: cellName}) newList = append(newList, xlsxC{R: cellName})
} }
rowData.C = newlist rowData.C = newList
for colIdx := range oldList { for colIdx := range oldList {
colData := &oldList[colIdx] colData := &oldList[colIdx]

@ -1048,7 +1048,7 @@ func TestNumberFormats(t *testing.T) {
{"A32", numFmt40, -8.8888666665555487, "(8.89)"}, {"A32", numFmt40, -8.8888666665555487, "(8.89)"},
} { } {
cell, styleID, value, expected := cases[0].(string), cases[1].(int), cases[2], cases[3].(string) 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)) assert.NoError(t, f.SetCellValue("Sheet1", cell, value))
result, err := f.GetCellValue("Sheet1", cell) result, err := f.GetCellValue("Sheet1", cell)
assert.NoError(t, err) assert.NoError(t, err)

@ -80,24 +80,12 @@ func (f *File) GetPageMargins(sheet string) (PageLayoutMarginsOptions, error) {
return opts, err return opts, err
} }
if ws.PageMargins != nil { if ws.PageMargins != nil {
if ws.PageMargins.Bottom != 0 { opts.Bottom = float64Ptr(ws.PageMargins.Bottom)
opts.Bottom = float64Ptr(ws.PageMargins.Bottom) opts.Footer = float64Ptr(ws.PageMargins.Footer)
} opts.Header = float64Ptr(ws.PageMargins.Header)
if ws.PageMargins.Footer != 0 { opts.Left = float64Ptr(ws.PageMargins.Left)
opts.Footer = float64Ptr(ws.PageMargins.Footer) opts.Right = float64Ptr(ws.PageMargins.Right)
} opts.Top = float64Ptr(ws.PageMargins.Top)
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)
}
} }
if ws.PrintOptions != nil { if ws.PrintOptions != nil {
opts.Horizontally = boolPtr(ws.PrintOptions.HorizontalCentered) opts.Horizontally = boolPtr(ws.PrintOptions.HorizontalCentered)
@ -106,7 +94,7 @@ func (f *File) GetPageMargins(sheet string) (PageLayoutMarginsOptions, error) {
return opts, err return opts, err
} }
// prepareSheetPr sheetPr element if which not exist. // prepareSheetPr create sheetPr element which not exist.
func (ws *xlsxWorksheet) prepareSheetPr() { func (ws *xlsxWorksheet) prepareSheetPr() {
if ws.SheetPr == nil { if ws.SheetPr == nil {
ws.SheetPr = new(xlsxSheetPr) ws.SheetPr = new(xlsxSheetPr)

@ -1,7 +1,6 @@
package excelize package excelize
import ( import (
"path/filepath"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -39,21 +38,6 @@ func TestGetPageMargins(t *testing.T) {
assert.EqualError(t, err, "sheet SheetN does not exist") 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) { func TestSetSheetProps(t *testing.T) {
f := NewFile() f := NewFile()
assert.NoError(t, f.SetSheetProps("Sheet1", nil)) assert.NoError(t, f.SetSheetProps("Sheet1", nil))

@ -369,11 +369,11 @@ func (sw *StreamWriter) SetRow(cell string, values []interface{}, opts ...RowOpt
if err != nil { if err != nil {
return err return err
} }
sw.rawData.WriteString(`<row r="`) _, _ = sw.rawData.WriteString(`<row r="`)
sw.rawData.WriteString(strconv.Itoa(row)) _, _ = sw.rawData.WriteString(strconv.Itoa(row))
sw.rawData.WriteString(`"`) _, _ = sw.rawData.WriteString(`"`)
sw.rawData.WriteString(attrs.String()) _, _ = sw.rawData.WriteString(attrs.String())
sw.rawData.WriteString(`>`) _, _ = sw.rawData.WriteString(`>`)
for i, val := range values { for i, val := range values {
if val == nil { if val == nil {
continue continue
@ -643,12 +643,12 @@ type bufferedWriter struct {
buf bytes.Buffer 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) { func (bw *bufferedWriter) Write(p []byte) (n int, err error) {
return bw.buf.Write(p) 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) { func (bw *bufferedWriter) WriteString(p string) (n int, err error) {
return bw.buf.WriteString(p) return bw.buf.WriteString(p)
} }

@ -235,7 +235,7 @@ func TestStreamSetRowNilValues(t *testing.T) {
file := NewFile() file := NewFile()
streamWriter, err := file.NewStreamWriter("Sheet1") streamWriter, err := file.NewStreamWriter("Sheet1")
assert.NoError(t, err) 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() streamWriter.Flush()
ws, err := file.workSheetReader("Sheet1") ws, err := file.workSheetReader("Sheet1")
assert.NoError(t, err) assert.NoError(t, err)

@ -2164,6 +2164,10 @@ func newFontColor(font *Font) *xlsxColor {
prepareFontColor() prepareFontColor()
fontColor.RGB = getPaletteColor(font.Color) fontColor.RGB = getPaletteColor(font.Color)
} }
if font.ColorIndexed >= 0 && font.ColorIndexed <= len(IndexedColorMapping)+1 {
prepareFontColor()
fontColor.Indexed = font.ColorIndexed
}
if font.ColorTheme != nil { if font.ColorTheme != nil {
prepareFontColor() prepareFontColor()
fontColor.Theme = font.ColorTheme fontColor.Theme = font.ColorTheme

@ -221,7 +221,7 @@ func parseAutoFilterOptions(opts string) (*autoFilterOptions, error) {
// //
// err := f.AutoFilter("Sheet1", "A1", "D4", `{"column":"B","expression":"x != blanks"}`) // 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 // criteria
// //
// It isn't sufficient to just specify the filter condition. You must also // It isn't sufficient to just specify the filter condition. You must also

@ -141,6 +141,26 @@ const (
ColorMappingTypeUnset int = -1 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. // 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"} 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"}

@ -334,16 +334,17 @@ type Border struct {
// Font directly maps the font settings of the fonts. // Font directly maps the font settings of the fonts.
type Font struct { type Font struct {
Bold bool `json:"bold"` Bold bool `json:"bold"`
Italic bool `json:"italic"` Italic bool `json:"italic"`
Underline string `json:"underline"` Underline string `json:"underline"`
Family string `json:"family"` Family string `json:"family"`
Size float64 `json:"size"` Size float64 `json:"size"`
Strike bool `json:"strike"` Strike bool `json:"strike"`
Color string `json:"color"` Color string `json:"color"`
ColorTheme *int `json:"color_theme"` ColorIndexed int `json:"color_indexed"`
ColorTint float64 `json:"color_tint"` ColorTheme *int `json:"color_theme"`
VertAlign string `json:"vertAlign"` ColorTint float64 `json:"color_tint"`
VertAlign string `json:"vertAlign"`
} }
// Fill directly maps the fill settings of the cells. // Fill directly maps the fill settings of the cells.

Loading…
Cancel
Save