forked from p30928647/excelize
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
This commit is contained in:
parent
f843a9ea56
commit
14c6a198ce
94
calc.go
94
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:
|
||||
|
|
61
calc_test.go
61
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(),
|
||||
|
|
1
cell.go
1
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
|
||||
|
|
90
cell_test.go
90
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, `<row r="1">
|
||||
<c r="A1"><v>2422.3000000000002</v></c>
|
||||
<c r="B1"><v>2422.3000000000002</v></c>
|
||||
<c r="C1"><v>12.4</v></c>
|
||||
<c r="D1"><v>964</v></c>
|
||||
<c r="E1"><v>1101.5999999999999</v></c>
|
||||
<c r="F1"><v>275.39999999999998</v></c>
|
||||
<c r="G1"><v>68.900000000000006</v></c>
|
||||
<c r="H1"><v>44385.208333333336</v></c>
|
||||
<c r="I1"><v>5.0999999999999996</v></c>
|
||||
<c r="J1"><v>5.1100000000000003</v></c>
|
||||
<c r="K1"><v>5.0999999999999996</v></c>
|
||||
<c r="L1"><v>5.1109999999999998</v></c>
|
||||
<c r="M1"><v>5.1111000000000004</v></c>
|
||||
<c r="N1"><v>2422.012345678</v></c>
|
||||
<c r="O1"><v>2422.0123456789</v></c>
|
||||
<c r="P1"><v>12.012345678901</v></c>
|
||||
<c r="Q1"><v>964</v></c>
|
||||
<c r="R1"><v>1101.5999999999999</v></c>
|
||||
<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="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="AB1" t="str"><v>1234567890123_4</v></c>
|
||||
<c r="AC1" t="str"><v>123456789_0123_4</v></c>
|
||||
<c r="AD1"><v>+0.0000000000000000002399999999999992E-4</v></c>
|
||||
<c r="AE1"><v>7.2399999999999992E-2</v></c>
|
||||
</row>`)))
|
||||
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `
|
||||
<row r="1"><c r="A1"><v>2422.3000000000002</v></c></row>
|
||||
<row r="2"><c r="A2"><v>2422.3000000000002</v></c></row>
|
||||
<row r="3"><c r="A3"><v>12.4</v></c></row>
|
||||
<row r="4"><c r="A4"><v>964</v></c></row>
|
||||
<row r="5"><c r="A5"><v>1101.5999999999999</v></c></row>
|
||||
<row r="6"><c r="A6"><v>275.39999999999998</v></c></row>
|
||||
<row r="7"><c r="A7"><v>68.900000000000006</v></c></row>
|
||||
<row r="8"><c r="A8"><v>44385.208333333336</v></c></row>
|
||||
<row r="9"><c r="A9"><v>5.0999999999999996</v></c></row>
|
||||
<row r="10"><c r="A10"><v>5.1100000000000003</v></c></row>
|
||||
<row r="11"><c r="A11"><v>5.0999999999999996</v></c></row>
|
||||
<row r="12"><c r="A12"><v>5.1109999999999998</v></c></row>
|
||||
<row r="13"><c r="A13"><v>5.1111000000000004</v></c></row>
|
||||
<row r="14"><c r="A14"><v>2422.012345678</v></c></row>
|
||||
<row r="15"><c r="A15"><v>2422.0123456789</v></c></row>
|
||||
<row r="16"><c r="A16"><v>12.012345678901</v></c></row>
|
||||
<row r="17"><c r="A17"><v>964</v></c></row>
|
||||
<row r="18"><c r="A18"><v>1101.5999999999999</v></c></row>
|
||||
<row r="19"><c r="A19"><v>275.39999999999998</v></c></row>
|
||||
<row r="20"><c r="A20"><v>68.900000000000006</v></c></row>
|
||||
<row r="21"><c r="A21"><v>8.8880000000000001E-2</v></c></row>
|
||||
<row r="22"><c r="A22"><v>4.0000000000000003e-5</v></c></row>
|
||||
<row r="23"><c r="A23"><v>2422.3000000000002</v></c></row>
|
||||
<row r="24"><c r="A24"><v>1101.5999999999999</v></c></row>
|
||||
<row r="25"><c r="A25"><v>275.39999999999998</v></c></row>
|
||||
<row r="26"><c r="A26"><v>68.900000000000006</v></c></row>
|
||||
<row r="27"><c r="A27"><v>1.1000000000000001</v></c></row>
|
||||
<row r="28"><c r="A28" t="str"><v>1234567890123_4</v></c></row>
|
||||
<row r="29"><c r="A29" t="str"><v>123456789_0123_4</v></c></row>
|
||||
<row r="30"><c r="A30"><v>+0.0000000000000000002399999999999992E-4</v></c></row>
|
||||
<row r="31"><c r="A31"><v>7.2399999999999992E-2</v></c></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
|
||||
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)
|
||||
|
||||
|
|
2
file.go
2
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()
|
||||
|
||||
|
|
55
rows.go
55
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
|
|||
// <c r="G15" s="1" />
|
||||
// </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).
|
||||
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]
|
||||
|
|
|
@ -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)
|
||||
|
|
26
sheetpr.go
26
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)
|
||||
|
|
|
@ -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))
|
||||
|
|
14
stream.go
14
stream.go
|
@ -369,11 +369,11 @@ func (sw *StreamWriter) SetRow(cell string, values []interface{}, opts ...RowOpt
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sw.rawData.WriteString(`<row r="`)
|
||||
sw.rawData.WriteString(strconv.Itoa(row))
|
||||
sw.rawData.WriteString(`"`)
|
||||
sw.rawData.WriteString(attrs.String())
|
||||
sw.rawData.WriteString(`>`)
|
||||
_, _ = sw.rawData.WriteString(`<row r="`)
|
||||
_, _ = sw.rawData.WriteString(strconv.Itoa(row))
|
||||
_, _ = sw.rawData.WriteString(`"`)
|
||||
_, _ = sw.rawData.WriteString(attrs.String())
|
||||
_, _ = 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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
2
table.go
2
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
|
||||
|
|
|
@ -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"}
|
||||
|
||||
|
|
21
xmlStyles.go
21
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.
|
||||
|
|
Loading…
Reference in New Issue