This fix #1665, supports getting formula string cell value
- Improve compatibility for absolute path drawing part - Fix incorrect table ID generated in the workbook which contains single table cells - Fix missing relationship parts in the content types in some cases - Upgrade number format parser to fix missing literal tokens in some cases - Update built-in zh-cn and zh-tw language number format - Ref #65, init new formula function: TEXT - Remove duplicate style-related variables - Update the unit tests
This commit is contained in:
parent
744236b4b8
commit
9c079e5eec
23
calc.go
23
calc.go
|
@ -757,6 +757,7 @@ type formulaFuncs struct {
|
|||
// TBILLPRICE
|
||||
// TBILLYIELD
|
||||
// TDIST
|
||||
// TEXT
|
||||
// TEXTJOIN
|
||||
// TIME
|
||||
// TIMEVALUE
|
||||
|
@ -14035,6 +14036,28 @@ func (fn *formulaFuncs) SUBSTITUTE(argsList *list.List) formulaArg {
|
|||
return newStringFormulaArg(pre + targetText.Value() + post)
|
||||
}
|
||||
|
||||
// TEXT function converts a supplied numeric value into text, in a
|
||||
// user-specified format. The syntax of the function is:
|
||||
//
|
||||
// TEXT(value,format_text)
|
||||
func (fn *formulaFuncs) TEXT(argsList *list.List) formulaArg {
|
||||
if argsList.Len() != 2 {
|
||||
return newErrorFormulaArg(formulaErrorVALUE, "TEXT requires 2 arguments")
|
||||
}
|
||||
value, fmtText := argsList.Front().Value.(formulaArg), argsList.Back().Value.(formulaArg)
|
||||
if value.Type == ArgError {
|
||||
return value
|
||||
}
|
||||
if fmtText.Type == ArgError {
|
||||
return fmtText
|
||||
}
|
||||
cellType := CellTypeNumber
|
||||
if num := value.ToNumber(); num.Type != ArgNumber {
|
||||
cellType = CellTypeSharedString
|
||||
}
|
||||
return newStringFormulaArg(format(value.Value(), fmtText.Value(), false, cellType, nil))
|
||||
}
|
||||
|
||||
// TEXTJOIN function joins together a series of supplied text strings into one
|
||||
// combined text string. The user can specify a delimiter to add between the
|
||||
// individual text items, if required. The syntax of the function is:
|
||||
|
|
13
calc_test.go
13
calc_test.go
|
@ -1834,6 +1834,15 @@ func TestCalcCellValue(t *testing.T) {
|
|||
"=SUBSTITUTE(\"abab\",\"x\",\"X\",2)": "abab",
|
||||
"=SUBSTITUTE(\"John is 5 years old\",\"John\",\"Jack\")": "Jack is 5 years old",
|
||||
"=SUBSTITUTE(\"John is 5 years old\",\"5\",\"6\")": "John is 6 years old",
|
||||
// TEXT
|
||||
"=TEXT(\"07/07/2015\",\"mm/dd/yyyy\")": "07/07/2015",
|
||||
"=TEXT(42192,\"mm/dd/yyyy\")": "07/07/2015",
|
||||
"=TEXT(42192,\"mmm dd yyyy\")": "Jul 07 2015",
|
||||
"=TEXT(0.75,\"hh:mm\")": "18:00",
|
||||
"=TEXT(36.363636,\"0.00\")": "36.36",
|
||||
"=TEXT(567.9,\"$#,##0.00\")": "$567.90",
|
||||
"=TEXT(-5,\"+ $#,##0.00;- $#,##0.00;$0.00\")": "- $5.00",
|
||||
"=TEXT(5,\"+ $#,##0.00;- $#,##0.00;$0.00\")": "+ $5.00",
|
||||
// TEXTJOIN
|
||||
"=TEXTJOIN(\"-\",TRUE,1,2,3,4)": "1-2-3-4",
|
||||
"=TEXTJOIN(A4,TRUE,A1:B2)": "1040205",
|
||||
|
@ -3866,6 +3875,10 @@ func TestCalcCellValue(t *testing.T) {
|
|||
"=SUBSTITUTE()": {"#VALUE!", "SUBSTITUTE requires 3 or 4 arguments"},
|
||||
"=SUBSTITUTE(\"\",\"\",\"\",\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
|
||||
"=SUBSTITUTE(\"\",\"\",\"\",0)": {"#VALUE!", "instance_num should be > 0"},
|
||||
// TEXT
|
||||
"=TEXT()": {"#VALUE!", "TEXT requires 2 arguments"},
|
||||
"=TEXT(NA(),\"\")": {"#N/A", "#N/A"},
|
||||
"=TEXT(0,NA())": {"#N/A", "#N/A"},
|
||||
// TEXTJOIN
|
||||
"=TEXTJOIN()": {"#VALUE!", "TEXTJOIN requires at least 3 arguments"},
|
||||
"=TEXTJOIN(\"\",\"\",1)": {"#VALUE!", "#VALUE!"},
|
||||
|
|
2
cell.go
2
cell.go
|
@ -590,6 +590,8 @@ func (c *xlsxC) getValueFrom(f *File, d *xlsxSST, raw bool) (string, error) {
|
|||
}
|
||||
}
|
||||
return f.formattedValue(c, raw, CellTypeSharedString)
|
||||
case "str":
|
||||
return c.V, nil
|
||||
case "inlineStr":
|
||||
if c.IS != nil {
|
||||
return f.formattedValue(&xlsxC{S: c.S, V: c.IS.String()}, raw, CellTypeInlineString)
|
||||
|
|
13
drawing.go
13
drawing.go
|
@ -25,8 +25,9 @@ import (
|
|||
func (f *File) prepareDrawing(ws *xlsxWorksheet, drawingID int, sheet, drawingXML string) (int, string) {
|
||||
sheetRelationshipsDrawingXML := "../drawings/drawing" + strconv.Itoa(drawingID) + ".xml"
|
||||
if ws.Drawing != nil {
|
||||
// The worksheet already has a picture or chart relationships, use the relationships drawing ../drawings/drawing%d.xml.
|
||||
sheetRelationshipsDrawingXML = f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID)
|
||||
// The worksheet already has a picture or chart relationships, use the
|
||||
// relationships drawing ../drawings/drawing%d.xml or /xl/drawings/drawing%d.xml.
|
||||
sheetRelationshipsDrawingXML = strings.ReplaceAll(f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID), "/xl/drawings/", "../drawings/")
|
||||
drawingID, _ = strconv.Atoi(strings.TrimSuffix(strings.TrimPrefix(sheetRelationshipsDrawingXML, "../drawings/drawing"), ".xml"))
|
||||
drawingXML = strings.ReplaceAll(sheetRelationshipsDrawingXML, "..", "xl")
|
||||
} else {
|
||||
|
@ -1247,9 +1248,11 @@ func (f *File) drawingParser(path string) (*xlsxWsDr, int, error) {
|
|||
)
|
||||
_, ok = f.Drawings.Load(path)
|
||||
if !ok {
|
||||
content := xlsxWsDr{}
|
||||
content.A = NameSpaceDrawingML.Value
|
||||
content.Xdr = NameSpaceDrawingMLSpreadSheet.Value
|
||||
content := xlsxWsDr{
|
||||
NS: NameSpaceDrawingMLSpreadSheet.Value,
|
||||
Xdr: NameSpaceDrawingMLSpreadSheet.Value,
|
||||
A: NameSpaceDrawingML.Value,
|
||||
}
|
||||
if _, ok = f.Pkg.Load(path); ok { // Append Model
|
||||
decodeWsDr := decodeWsDr{}
|
||||
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(path)))).
|
||||
|
|
8
go.mod
8
go.mod
|
@ -8,9 +8,9 @@ require (
|
|||
github.com/richardlehane/msoleps v1.0.3 // indirect
|
||||
github.com/stretchr/testify v1.8.0
|
||||
github.com/xuri/efp v0.0.0-20230802181842-ad255f2331ca
|
||||
github.com/xuri/nfp v0.0.0-20230819163627-dc951e3ffe1a
|
||||
golang.org/x/crypto v0.12.0
|
||||
github.com/xuri/nfp v0.0.0-20230918160701-e5a3f5b24785
|
||||
golang.org/x/crypto v0.13.0
|
||||
golang.org/x/image v0.11.0
|
||||
golang.org/x/net v0.14.0
|
||||
golang.org/x/text v0.12.0
|
||||
golang.org/x/net v0.15.0
|
||||
golang.org/x/text v0.13.0
|
||||
)
|
||||
|
|
19
go.sum
19
go.sum
|
@ -17,13 +17,13 @@ github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PK
|
|||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/xuri/efp v0.0.0-20230802181842-ad255f2331ca h1:uvPMDVyP7PXMMioYdyPH+0O+Ta/UO1WFfNYMO3Wz0eg=
|
||||
github.com/xuri/efp v0.0.0-20230802181842-ad255f2331ca/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
|
||||
github.com/xuri/nfp v0.0.0-20230819163627-dc951e3ffe1a h1:Mw2VNrNNNjDtw68VsEj2+st+oCSn4Uz7vZw6TbhcV1o=
|
||||
github.com/xuri/nfp v0.0.0-20230819163627-dc951e3ffe1a/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
|
||||
github.com/xuri/nfp v0.0.0-20230918160701-e5a3f5b24785 h1:FG9hcK7lhf3w/Y2NRUKy/mopsH0Oy6P1rib1KWXAie0=
|
||||
github.com/xuri/nfp v0.0.0-20230918160701-e5a3f5b24785/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
|
||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/image v0.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo=
|
||||
golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
|
@ -33,8 +33,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
|
|||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
|
||||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
@ -45,19 +45,20 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
|
|
20
numfmt.go
20
numfmt.go
|
@ -108,16 +108,16 @@ var (
|
|||
31: "yyyy\"年\"m\"月\"d\"日\"",
|
||||
32: "hh\"時\"mm\"分\"",
|
||||
33: "hh\"時\"mm\"分\"ss\"秒\"",
|
||||
34: "上午/下午 hh\"時\"mm\"分\"",
|
||||
35: "上午/下午 hh\"時\"mm\"分\"ss\"秒\"",
|
||||
34: "上午/下午hh\"時\"mm\"分\"",
|
||||
35: "上午/下午hh\"時\"mm\"分\"ss\"秒\"",
|
||||
36: "[$-404]e/m/d",
|
||||
50: "[$-404]e/m/d",
|
||||
51: "[$-404]e\"年\"m\"月\"d\"日\"",
|
||||
52: "上午/下午 hh\"時\"mm\"分\"",
|
||||
53: "上午/下午 hh\"時\"mm\"分\"ss\"秒\"",
|
||||
52: "上午/下午hh\"時\"mm\"分\"",
|
||||
53: "上午/下午hh\"時\"mm\"分\"ss\"秒\"",
|
||||
54: "[$-404]e\"年\"m\"月\"d\"日\"",
|
||||
55: "上午/下午 hh\"時\"mm\"分\"",
|
||||
56: "上午/下午 hh\"時\"mm\"分\"ss\"秒\"",
|
||||
55: "上午/下午hh\"時\"mm\"分\"",
|
||||
56: "上午/下午hh\"時\"mm\"分\"ss\"秒\"",
|
||||
57: "[$-404]e/m/d",
|
||||
58: "[$-404]e\"年\"m\"月\"d\"日\"",
|
||||
},
|
||||
|
@ -129,16 +129,16 @@ var (
|
|||
31: "yyyy\"年\"m\"月\"d\"日\"",
|
||||
32: "h\"时\"mm\"分\"",
|
||||
33: "h\"时\"mm\"分\"ss\"秒\"",
|
||||
34: "上午/下午 h\"时\"mm\"分\"",
|
||||
35: "上午/下午 h\"时\"mm\"分\"ss\"秒\"",
|
||||
34: "上午/下午h\"时\"mm\"分\"",
|
||||
35: "上午/下午h\"时\"mm\"分\"ss\"秒\"",
|
||||
36: "yyyy\"年\"m\"月\"",
|
||||
50: "yyyy\"年\"m\"月\"",
|
||||
51: "m\"月\"d\"日\"",
|
||||
52: "yyyy\"年\"m\"月\"",
|
||||
53: "m\"月\"d\"日\"",
|
||||
54: "m\"月\"d\"日\"",
|
||||
55: "上午/下午 h\"时\"mm\"分\"",
|
||||
56: "上午/下午 h\"时\"mm\"分\"ss\"秒\"",
|
||||
55: "上午/下午h\"时\"mm\"分\"",
|
||||
56: "上午/下午h\"时\"mm\"分\"ss\"秒\"",
|
||||
57: "yyyy\"年\"m\"月\"",
|
||||
58: "m\"月\"d\"日\"",
|
||||
},
|
||||
|
|
|
@ -72,6 +72,7 @@ func TestNumFmt(t *testing.T) {
|
|||
{"43528", "[$-111]MM/DD/YYYY", "43528"},
|
||||
{"43528", "[$US-409]MM/DD/YYYY", "US03/04/2019"},
|
||||
{"43543.586539351854", "AM/PM h h:mm", "PM 14 2:04"},
|
||||
{"45186", "DD.MM.YYYY", "17.09.2023"},
|
||||
{"text", "AM/PM h h:mm", "text"},
|
||||
{"43466.189571759256", "[$-404]aaa;@", "週二"},
|
||||
{"43466.189571759256", "[$-404]aaaa;@", "星期二"},
|
||||
|
|
|
@ -36,7 +36,7 @@ import (
|
|||
// Caption specifies the caption of the slicer, this setting is optional.
|
||||
//
|
||||
// Macro used for set macro for the slicer, the workbook extension should be
|
||||
// XLSM or XLTM
|
||||
// XLSM or XLTM.
|
||||
//
|
||||
// Width specifies the width of the slicer, this setting is optional.
|
||||
//
|
||||
|
|
209
styles.go
209
styles.go
|
@ -1096,6 +1096,73 @@ var (
|
|||
{Stop: []*xlsxGradientFillStop{{}, {Position: 1}}, Type: "path", Bottom: 1, Left: 1, Right: 1, Top: 1},
|
||||
{Stop: []*xlsxGradientFillStop{{}, {Position: 1}}, Type: "path", Bottom: 0.5, Left: 0.5, Right: 0.5, Top: 0.5},
|
||||
}
|
||||
// getXfIDFuncs provides a function to get xfID by given style.
|
||||
getXfIDFuncs = map[string]func(int, xlsxXf, *Style) bool{
|
||||
"numFmt": func(numFmtID int, xf xlsxXf, style *Style) bool {
|
||||
if style.CustomNumFmt == nil && numFmtID == -1 {
|
||||
return xf.NumFmtID != nil && *xf.NumFmtID == 0
|
||||
}
|
||||
if style.NegRed || (style.DecimalPlaces != nil && *style.DecimalPlaces != 2) {
|
||||
return false
|
||||
}
|
||||
return xf.NumFmtID != nil && *xf.NumFmtID == numFmtID
|
||||
},
|
||||
"font": func(fontID int, xf xlsxXf, style *Style) bool {
|
||||
if style.Font == nil {
|
||||
return (xf.FontID == nil || *xf.FontID == 0) && (xf.ApplyFont == nil || !*xf.ApplyFont)
|
||||
}
|
||||
return xf.FontID != nil && *xf.FontID == fontID && xf.ApplyFont != nil && *xf.ApplyFont
|
||||
},
|
||||
"fill": func(fillID int, xf xlsxXf, style *Style) bool {
|
||||
if style.Fill.Type == "" {
|
||||
return (xf.FillID == nil || *xf.FillID == 0) && (xf.ApplyFill == nil || !*xf.ApplyFill)
|
||||
}
|
||||
return xf.FillID != nil && *xf.FillID == fillID && xf.ApplyFill != nil && *xf.ApplyFill
|
||||
},
|
||||
"border": func(borderID int, xf xlsxXf, style *Style) bool {
|
||||
if len(style.Border) == 0 {
|
||||
return (xf.BorderID == nil || *xf.BorderID == 0) && (xf.ApplyBorder == nil || !*xf.ApplyBorder)
|
||||
}
|
||||
return xf.BorderID != nil && *xf.BorderID == borderID && xf.ApplyBorder != nil && *xf.ApplyBorder
|
||||
},
|
||||
"alignment": func(ID int, xf xlsxXf, style *Style) bool {
|
||||
if style.Alignment == nil {
|
||||
return xf.ApplyAlignment == nil || !*xf.ApplyAlignment
|
||||
}
|
||||
return reflect.DeepEqual(xf.Alignment, newAlignment(style))
|
||||
},
|
||||
"protection": func(ID int, xf xlsxXf, style *Style) bool {
|
||||
if style.Protection == nil {
|
||||
return xf.ApplyProtection == nil || !*xf.ApplyProtection
|
||||
}
|
||||
return reflect.DeepEqual(xf.Protection, newProtection(style)) && xf.ApplyProtection != nil && *xf.ApplyProtection
|
||||
},
|
||||
}
|
||||
// drawContFmtFunc defines functions to create conditional formats.
|
||||
drawContFmtFunc = map[string]func(p int, ct, GUID string, fmtCond *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule){
|
||||
"cellIs": drawCondFmtCellIs,
|
||||
"top10": drawCondFmtTop10,
|
||||
"aboveAverage": drawCondFmtAboveAverage,
|
||||
"duplicateValues": drawCondFmtDuplicateUniqueValues,
|
||||
"uniqueValues": drawCondFmtDuplicateUniqueValues,
|
||||
"2_color_scale": drawCondFmtColorScale,
|
||||
"3_color_scale": drawCondFmtColorScale,
|
||||
"dataBar": drawCondFmtDataBar,
|
||||
"expression": drawCondFmtExp,
|
||||
"iconSet": drawCondFmtIconSet,
|
||||
}
|
||||
// extractContFmtFunc defines functions to get conditional formats.
|
||||
extractContFmtFunc = map[string]func(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions{
|
||||
"cellIs": extractCondFmtCellIs,
|
||||
"top10": extractCondFmtTop10,
|
||||
"aboveAverage": extractCondFmtAboveAverage,
|
||||
"duplicateValues": extractCondFmtDuplicateUniqueValues,
|
||||
"uniqueValues": extractCondFmtDuplicateUniqueValues,
|
||||
"colorScale": extractCondFmtColorScale,
|
||||
"dataBar": extractCondFmtDataBar,
|
||||
"expression": extractCondFmtExp,
|
||||
"iconSet": extractCondFmtIconSet,
|
||||
}
|
||||
)
|
||||
|
||||
// getThemeColor provides a function to convert theme color or index color to
|
||||
|
@ -1329,49 +1396,6 @@ func (f *File) GetStyle(idx int) (*Style, error) {
|
|||
return style, nil
|
||||
}
|
||||
|
||||
// getXfIDFuncs provides a function to get xfID by given style.
|
||||
var getXfIDFuncs = map[string]func(int, xlsxXf, *Style) bool{
|
||||
"numFmt": func(numFmtID int, xf xlsxXf, style *Style) bool {
|
||||
if style.CustomNumFmt == nil && numFmtID == -1 {
|
||||
return xf.NumFmtID != nil && *xf.NumFmtID == 0
|
||||
}
|
||||
if style.NegRed || (style.DecimalPlaces != nil && *style.DecimalPlaces != 2) {
|
||||
return false
|
||||
}
|
||||
return xf.NumFmtID != nil && *xf.NumFmtID == numFmtID
|
||||
},
|
||||
"font": func(fontID int, xf xlsxXf, style *Style) bool {
|
||||
if style.Font == nil {
|
||||
return (xf.FontID == nil || *xf.FontID == 0) && (xf.ApplyFont == nil || !*xf.ApplyFont)
|
||||
}
|
||||
return xf.FontID != nil && *xf.FontID == fontID && xf.ApplyFont != nil && *xf.ApplyFont
|
||||
},
|
||||
"fill": func(fillID int, xf xlsxXf, style *Style) bool {
|
||||
if style.Fill.Type == "" {
|
||||
return (xf.FillID == nil || *xf.FillID == 0) && (xf.ApplyFill == nil || !*xf.ApplyFill)
|
||||
}
|
||||
return xf.FillID != nil && *xf.FillID == fillID && xf.ApplyFill != nil && *xf.ApplyFill
|
||||
},
|
||||
"border": func(borderID int, xf xlsxXf, style *Style) bool {
|
||||
if len(style.Border) == 0 {
|
||||
return (xf.BorderID == nil || *xf.BorderID == 0) && (xf.ApplyBorder == nil || !*xf.ApplyBorder)
|
||||
}
|
||||
return xf.BorderID != nil && *xf.BorderID == borderID && xf.ApplyBorder != nil && *xf.ApplyBorder
|
||||
},
|
||||
"alignment": func(ID int, xf xlsxXf, style *Style) bool {
|
||||
if style.Alignment == nil {
|
||||
return xf.ApplyAlignment == nil || !*xf.ApplyAlignment
|
||||
}
|
||||
return reflect.DeepEqual(xf.Alignment, newAlignment(style))
|
||||
},
|
||||
"protection": func(ID int, xf xlsxXf, style *Style) bool {
|
||||
if style.Protection == nil {
|
||||
return xf.ApplyProtection == nil || !*xf.ApplyProtection
|
||||
}
|
||||
return reflect.DeepEqual(xf.Protection, newProtection(style)) && xf.ApplyProtection != nil && *xf.ApplyProtection
|
||||
},
|
||||
}
|
||||
|
||||
// getStyleID provides a function to get styleID by given style. If given
|
||||
// style does not exist, will return -1.
|
||||
func (f *File) getStyleID(ss *xlsxStyleSheet, style *Style) (int, error) {
|
||||
|
@ -1740,54 +1764,13 @@ func getFillID(styleSheet *xlsxStyleSheet, style *Style) (fillID int) {
|
|||
// newFills provides a function to add fill elements in the styles.xml by
|
||||
// given cell format settings.
|
||||
func newFills(style *Style, fg bool) *xlsxFill {
|
||||
patterns := []string{
|
||||
"none",
|
||||
"solid",
|
||||
"mediumGray",
|
||||
"darkGray",
|
||||
"lightGray",
|
||||
"darkHorizontal",
|
||||
"darkVertical",
|
||||
"darkDown",
|
||||
"darkUp",
|
||||
"darkGrid",
|
||||
"darkTrellis",
|
||||
"lightHorizontal",
|
||||
"lightVertical",
|
||||
"lightDown",
|
||||
"lightUp",
|
||||
"lightGrid",
|
||||
"lightTrellis",
|
||||
"gray125",
|
||||
"gray0625",
|
||||
}
|
||||
variants := []xlsxGradientFill{
|
||||
{Degree: 90, Stop: []*xlsxGradientFillStop{{}, {Position: 1}}},
|
||||
{Degree: 270, Stop: []*xlsxGradientFillStop{{}, {Position: 1}}},
|
||||
{Degree: 90, Stop: []*xlsxGradientFillStop{{}, {Position: 0.5}, {Position: 1}}},
|
||||
{Stop: []*xlsxGradientFillStop{{}, {Position: 1}}},
|
||||
{Degree: 180, Stop: []*xlsxGradientFillStop{{}, {Position: 1}}},
|
||||
{Stop: []*xlsxGradientFillStop{{}, {Position: 0.5}, {Position: 1}}},
|
||||
{Degree: 45, Stop: []*xlsxGradientFillStop{{}, {Position: 1}}},
|
||||
{Degree: 255, Stop: []*xlsxGradientFillStop{{}, {Position: 1}}},
|
||||
{Degree: 45, Stop: []*xlsxGradientFillStop{{}, {Position: 0.5}, {Position: 1}}},
|
||||
{Degree: 135, Stop: []*xlsxGradientFillStop{{}, {Position: 1}}},
|
||||
{Degree: 315, Stop: []*xlsxGradientFillStop{{}, {Position: 1}}},
|
||||
{Degree: 135, Stop: []*xlsxGradientFillStop{{}, {Position: 0.5}, {Position: 1}}},
|
||||
{Stop: []*xlsxGradientFillStop{{}, {Position: 1}}, Type: "path"},
|
||||
{Stop: []*xlsxGradientFillStop{{}, {Position: 1}}, Type: "path", Left: 1, Right: 1},
|
||||
{Stop: []*xlsxGradientFillStop{{}, {Position: 1}}, Type: "path", Bottom: 1, Top: 1},
|
||||
{Stop: []*xlsxGradientFillStop{{}, {Position: 1}}, Type: "path", Bottom: 1, Left: 1, Right: 1, Top: 1},
|
||||
{Stop: []*xlsxGradientFillStop{{}, {Position: 1}}, Type: "path", Bottom: 0.5, Left: 0.5, Right: 0.5, Top: 0.5},
|
||||
}
|
||||
|
||||
var fill xlsxFill
|
||||
switch style.Fill.Type {
|
||||
case "gradient":
|
||||
if len(style.Fill.Color) != 2 || style.Fill.Shading < 0 || style.Fill.Shading > 16 {
|
||||
break
|
||||
}
|
||||
gradient := variants[style.Fill.Shading]
|
||||
gradient := styleFillVariants[style.Fill.Shading]
|
||||
gradient.Stop[0].Color.RGB = getPaletteColor(style.Fill.Color[0])
|
||||
gradient.Stop[1].Color.RGB = getPaletteColor(style.Fill.Color[1])
|
||||
if len(gradient.Stop) == 3 {
|
||||
|
@ -1802,7 +1785,7 @@ func newFills(style *Style, fg bool) *xlsxFill {
|
|||
break
|
||||
}
|
||||
var pattern xlsxPatternFill
|
||||
pattern.PatternType = patterns[style.Fill.Pattern]
|
||||
pattern.PatternType = styleFillPatterns[style.Fill.Pattern]
|
||||
if fg {
|
||||
if pattern.FgColor == nil {
|
||||
pattern.FgColor = new(xlsxColor)
|
||||
|
@ -1871,23 +1854,6 @@ func getBorderID(styleSheet *xlsxStyleSheet, style *Style) (borderID int) {
|
|||
// newBorders provides a function to add border elements in the styles.xml by
|
||||
// given borders format settings.
|
||||
func newBorders(style *Style) *xlsxBorder {
|
||||
styles := []string{
|
||||
"none",
|
||||
"thin",
|
||||
"medium",
|
||||
"dashed",
|
||||
"dotted",
|
||||
"thick",
|
||||
"double",
|
||||
"hair",
|
||||
"mediumDashed",
|
||||
"dashDot",
|
||||
"mediumDashDot",
|
||||
"dashDotDot",
|
||||
"mediumDashDotDot",
|
||||
"slantDashDot",
|
||||
}
|
||||
|
||||
var border xlsxBorder
|
||||
for _, v := range style.Border {
|
||||
if 0 <= v.Style && v.Style < 14 {
|
||||
|
@ -1895,23 +1861,23 @@ func newBorders(style *Style) *xlsxBorder {
|
|||
color.RGB = getPaletteColor(v.Color)
|
||||
switch v.Type {
|
||||
case "left":
|
||||
border.Left.Style = styles[v.Style]
|
||||
border.Left.Style = styleBorders[v.Style]
|
||||
border.Left.Color = &color
|
||||
case "right":
|
||||
border.Right.Style = styles[v.Style]
|
||||
border.Right.Style = styleBorders[v.Style]
|
||||
border.Right.Color = &color
|
||||
case "top":
|
||||
border.Top.Style = styles[v.Style]
|
||||
border.Top.Style = styleBorders[v.Style]
|
||||
border.Top.Color = &color
|
||||
case "bottom":
|
||||
border.Bottom.Style = styles[v.Style]
|
||||
border.Bottom.Style = styleBorders[v.Style]
|
||||
border.Bottom.Color = &color
|
||||
case "diagonalUp":
|
||||
border.Diagonal.Style = styles[v.Style]
|
||||
border.Diagonal.Style = styleBorders[v.Style]
|
||||
border.Diagonal.Color = &color
|
||||
border.DiagonalUp = true
|
||||
case "diagonalDown":
|
||||
border.Diagonal.Style = styles[v.Style]
|
||||
border.Diagonal.Style = styleBorders[v.Style]
|
||||
border.Diagonal.Color = &color
|
||||
border.DiagonalDown = true
|
||||
}
|
||||
|
@ -2551,19 +2517,6 @@ func (f *File) SetCellStyle(sheet, hCell, vCell string, styleID int) error {
|
|||
// cells. When this parameter is set then subsequent rules are not evaluated
|
||||
// if the current rule is true.
|
||||
func (f *File) SetConditionalFormat(sheet, rangeRef string, opts []ConditionalFormatOptions) error {
|
||||
drawContFmtFunc := map[string]func(p int, ct, GUID string, fmtCond *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule){
|
||||
"cellIs": drawCondFmtCellIs,
|
||||
"top10": drawCondFmtTop10,
|
||||
"aboveAverage": drawCondFmtAboveAverage,
|
||||
"duplicateValues": drawCondFmtDuplicateUniqueValues,
|
||||
"uniqueValues": drawCondFmtDuplicateUniqueValues,
|
||||
"2_color_scale": drawCondFmtColorScale,
|
||||
"3_color_scale": drawCondFmtColorScale,
|
||||
"dataBar": drawCondFmtDataBar,
|
||||
"expression": drawCondFmtExp,
|
||||
"iconSet": drawCondFmtIconSet,
|
||||
}
|
||||
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -2833,18 +2786,6 @@ func extractCondFmtIconSet(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatO
|
|||
// GetConditionalFormats returns conditional format settings by given worksheet
|
||||
// name.
|
||||
func (f *File) GetConditionalFormats(sheet string) (map[string][]ConditionalFormatOptions, error) {
|
||||
extractContFmtFunc := map[string]func(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions{
|
||||
"cellIs": extractCondFmtCellIs,
|
||||
"top10": extractCondFmtTop10,
|
||||
"aboveAverage": extractCondFmtAboveAverage,
|
||||
"duplicateValues": extractCondFmtDuplicateUniqueValues,
|
||||
"uniqueValues": extractCondFmtDuplicateUniqueValues,
|
||||
"colorScale": extractCondFmtColorScale,
|
||||
"dataBar": extractCondFmtDataBar,
|
||||
"expression": extractCondFmtExp,
|
||||
"iconSet": extractCondFmtIconSet,
|
||||
}
|
||||
|
||||
conditionalFormats := make(map[string][]ConditionalFormatOptions)
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
if err != nil {
|
||||
|
|
13
table.go
13
table.go
|
@ -216,6 +216,19 @@ func (f *File) DeleteTable(name string) error {
|
|||
func (f *File) countTables() int {
|
||||
count := 0
|
||||
f.Pkg.Range(func(k, v interface{}) bool {
|
||||
if strings.Contains(k.(string), "xl/tables/tableSingleCells") {
|
||||
var cells xlsxSingleXmlCells
|
||||
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(v.([]byte)))).
|
||||
Decode(&cells); err != nil && err != io.EOF {
|
||||
count++
|
||||
return true
|
||||
}
|
||||
for _, cell := range cells.SingleXmlCell {
|
||||
if count < cell.ID {
|
||||
count = cell.ID
|
||||
}
|
||||
}
|
||||
}
|
||||
if strings.Contains(k.(string), "xl/tables/table") {
|
||||
var t xlsxTable
|
||||
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(v.([]byte)))).
|
||||
|
|
|
@ -76,6 +76,15 @@ func TestAddTable(t *testing.T) {
|
|||
f = NewFile()
|
||||
f.Pkg.Store("xl/tables/table1.xml", MacintoshCyrillicCharset)
|
||||
assert.NoError(t, f.AddTable("Sheet1", &Table{Range: "A1:B2"}))
|
||||
assert.NoError(t, f.Close())
|
||||
f = NewFile()
|
||||
// Test add table with workbook with single cells parts
|
||||
f.Pkg.Store("xl/tables/tableSingleCells1.xml", []byte("<singleXmlCells><singleXmlCell id=\"2\" r=\"A1\" connectionId=\"2\" /></singleXmlCells>"))
|
||||
assert.NoError(t, f.AddTable("Sheet1", &Table{Range: "A1:B2"}))
|
||||
// Test add table with workbook with unsupported charset single cells parts
|
||||
f.Pkg.Store("xl/tables/tableSingleCells1.xml", MacintoshCyrillicCharset)
|
||||
assert.NoError(t, f.AddTable("Sheet1", &Table{Range: "A1:B2"}))
|
||||
assert.NoError(t, f.Close())
|
||||
}
|
||||
|
||||
func TestGetTables(t *testing.T) {
|
||||
|
|
|
@ -242,6 +242,7 @@ type xlsxPoint2D struct {
|
|||
type xlsxWsDr struct {
|
||||
mu sync.Mutex
|
||||
XMLName xml.Name `xml:"xdr:wsDr"`
|
||||
NS string `xml:"xmlns,attr,omitempty"`
|
||||
A string `xml:"xmlns:a,attr,omitempty"`
|
||||
Xdr string `xml:"xmlns:xdr,attr,omitempty"`
|
||||
R string `xml:"xmlns:r,attr,omitempty"`
|
||||
|
|
29
xmlTable.go
29
xmlTable.go
|
@ -196,6 +196,35 @@ type xlsxTableStyleInfo struct {
|
|||
ShowColumnStripes bool `xml:"showColumnStripes,attr"`
|
||||
}
|
||||
|
||||
// xlsxSingleXmlCells is a single cell table is generated from an XML mapping.
|
||||
// These really just look like regular cells to the spreadsheet user, but shall
|
||||
// be implemented as Tables "under the covers."
|
||||
type xlsxSingleXmlCells struct {
|
||||
XMLName xml.Name `xml:"singleXmlCells"`
|
||||
SingleXmlCell []xlsxSingleXmlCell `xml:"singleXmlCell"`
|
||||
}
|
||||
|
||||
// xlsxSingleXmlCell is a element represents the table properties for a single
|
||||
// cell XML table.
|
||||
type xlsxSingleXmlCell struct {
|
||||
XMLName xml.Name `xml:"singleXmlCell"`
|
||||
ID int `xml:"id,attr"`
|
||||
R string `xml:"r,attr"`
|
||||
ConnectionID int `xml:"connectionId,attr"`
|
||||
XMLCellPr xlsxXmlCellPr `xml:"xmlCellPr"`
|
||||
ExtLst *xlsxInnerXML `xml:"extLst"`
|
||||
}
|
||||
|
||||
// xlsxXmlCellPr is a element stores the XML properties for the cell of a single
|
||||
// cell xml table.
|
||||
type xlsxXmlCellPr struct {
|
||||
XMLName xml.Name `xml:"xmlCellPr"`
|
||||
ID int `xml:"id,attr"`
|
||||
UniqueName string `xml:"uniqueName,attr,omitempty"`
|
||||
XMLPr *xlsxInnerXML `xml:"xmlPr"`
|
||||
ExtLst *xlsxInnerXML `xml:"extLst"`
|
||||
}
|
||||
|
||||
// Table directly maps the format settings of the table.
|
||||
type Table struct {
|
||||
tID int
|
||||
|
|
Loading…
Reference in New Issue