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
// 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:

@ -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(),

@ -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

@ -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)

@ -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()

@ -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)

@ -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))

@ -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

@ -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"}

@ -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…
Cancel
Save