Compare commits
3 Commits
d81b4c8661
...
9a38657515
Author | SHA1 | Date |
---|---|---|
|
9a38657515 | |
|
d21b598235 | |
|
30c4cd70e0 |
|
@ -259,12 +259,12 @@ func (f *File) adjustCellRef(cellRef string, dir adjustDirection, num, offset in
|
|||
return "", err
|
||||
}
|
||||
if dir == columns {
|
||||
if offset < 0 && coordinates[0] == coordinates[2] {
|
||||
if offset < 0 && coordinates[0] == coordinates[2] && num == coordinates[0] {
|
||||
continue
|
||||
}
|
||||
coordinates = applyOffset(coordinates, 0, 2, MaxColumns)
|
||||
} else {
|
||||
if offset < 0 && coordinates[1] == coordinates[3] {
|
||||
if offset < 0 && coordinates[1] == coordinates[3] && num == coordinates[1] {
|
||||
continue
|
||||
}
|
||||
coordinates = applyOffset(coordinates, 1, 3, TotalRows)
|
||||
|
|
|
@ -1020,6 +1020,57 @@ func TestAdjustConditionalFormats(t *testing.T) {
|
|||
|
||||
ws.(*xlsxWorksheet).ConditionalFormatting[0] = nil
|
||||
assert.NoError(t, f.RemoveCol("Sheet1", "B"))
|
||||
|
||||
t.Run("for_remove_conditional_formats_column", func(t *testing.T) {
|
||||
f := NewFile()
|
||||
format := []ConditionalFormatOptions{{
|
||||
Type: "data_bar",
|
||||
Criteria: "=",
|
||||
MinType: "min",
|
||||
MaxType: "max",
|
||||
BarColor: "#638EC6",
|
||||
}}
|
||||
assert.NoError(t, f.SetConditionalFormat("Sheet1", "D2:D3", format))
|
||||
assert.NoError(t, f.SetConditionalFormat("Sheet1", "D5", format))
|
||||
assert.NoError(t, f.RemoveCol("Sheet1", "D"))
|
||||
opts, err := f.GetConditionalFormats("Sheet1")
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, opts, 0)
|
||||
})
|
||||
t.Run("for_remove_conditional_formats_row", func(t *testing.T) {
|
||||
f := NewFile()
|
||||
format := []ConditionalFormatOptions{{
|
||||
Type: "data_bar",
|
||||
Criteria: "=",
|
||||
MinType: "min",
|
||||
MaxType: "max",
|
||||
BarColor: "#638EC6",
|
||||
}}
|
||||
assert.NoError(t, f.SetConditionalFormat("Sheet1", "D2:E2", format))
|
||||
assert.NoError(t, f.SetConditionalFormat("Sheet1", "F2", format))
|
||||
assert.NoError(t, f.RemoveRow("Sheet1", 2))
|
||||
opts, err := f.GetConditionalFormats("Sheet1")
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, opts, 0)
|
||||
})
|
||||
t.Run("for_adjust_conditional_formats_row", func(t *testing.T) {
|
||||
f := NewFile()
|
||||
format := []ConditionalFormatOptions{{
|
||||
Type: "data_bar",
|
||||
Criteria: "=",
|
||||
MinType: "min",
|
||||
MaxType: "max",
|
||||
BarColor: "#638EC6",
|
||||
}}
|
||||
assert.NoError(t, f.SetConditionalFormat("Sheet1", "D2:D3", format))
|
||||
assert.NoError(t, f.SetConditionalFormat("Sheet1", "D5", format))
|
||||
assert.NoError(t, f.RemoveRow("Sheet1", 1))
|
||||
opts, err := f.GetConditionalFormats("Sheet1")
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, opts, 2)
|
||||
assert.Equal(t, format, opts["D1:D2"])
|
||||
assert.Equal(t, format, opts["D4:D4"])
|
||||
})
|
||||
}
|
||||
|
||||
func TestAdjustDataValidations(t *testing.T) {
|
||||
|
|
4
calc.go
4
calc.go
|
@ -14459,7 +14459,7 @@ func (fn *formulaFuncs) VALUE(argsList *list.List) formulaArg {
|
|||
value, _ := decimal.Float64()
|
||||
return newNumberFormulaArg(value * percent)
|
||||
}
|
||||
dateValue, timeValue, errTime, errDate := 0.0, 0.0, false, false
|
||||
dateValue, timeValue, errTime := 0.0, 0.0, false
|
||||
if !isDateOnlyFmt(text) {
|
||||
h, m, s, _, _, err := strToTime(text)
|
||||
errTime = err.Type == ArgError
|
||||
|
@ -14468,7 +14468,7 @@ func (fn *formulaFuncs) VALUE(argsList *list.List) formulaArg {
|
|||
}
|
||||
}
|
||||
y, m, d, _, err := strToDate(text)
|
||||
errDate = err.Type == ArgError
|
||||
errDate := err.Type == ArgError
|
||||
if !errDate {
|
||||
dateValue = daysBetween(excelMinTime1900.Unix(), makeDate(y, time.Month(m), d)) + 1
|
||||
}
|
||||
|
|
|
@ -62,6 +62,10 @@ type PivotTableOptions struct {
|
|||
}
|
||||
|
||||
// PivotTableField directly maps the field settings of the pivot table.
|
||||
//
|
||||
// Name specifies the name of the data field. Maximum 255 characters
|
||||
// are allowed in data field name, excess characters will be truncated.
|
||||
//
|
||||
// Subtotal specifies the aggregation function that applies to this data
|
||||
// field. The default value is sum. The possible values for this attribute
|
||||
// are:
|
||||
|
@ -78,8 +82,9 @@ type PivotTableOptions struct {
|
|||
// Var
|
||||
// Varp
|
||||
//
|
||||
// Name specifies the name of the data field. Maximum 255 characters
|
||||
// are allowed in data field name, excess characters will be truncated.
|
||||
// NumFmt specifies the number format ID of the data field, this filed only
|
||||
// accepts built-in number format ID and does not support custom number format
|
||||
// expression currently.
|
||||
type PivotTableField struct {
|
||||
Compact bool
|
||||
Data string
|
||||
|
@ -87,6 +92,7 @@ type PivotTableField struct {
|
|||
Outline bool
|
||||
Subtotal string
|
||||
DefaultSubtotal bool
|
||||
NumFmt int
|
||||
}
|
||||
|
||||
// AddPivotTable provides the method to add pivot table by given pivot table
|
||||
|
@ -452,6 +458,7 @@ func (f *File) addPivotDataFields(pt *xlsxPivotTableDefinition, opts *PivotTable
|
|||
}
|
||||
dataFieldsSubtotals := f.getPivotTableFieldsSubtotal(opts.Data)
|
||||
dataFieldsName := f.getPivotTableFieldsName(opts.Data)
|
||||
dataFieldsNumFmtID := f.getPivotTableFieldsNumFmtID(opts.Data)
|
||||
for idx, dataField := range dataFieldsIndex {
|
||||
if pt.DataFields == nil {
|
||||
pt.DataFields = &xlsxDataFields{}
|
||||
|
@ -460,6 +467,7 @@ func (f *File) addPivotDataFields(pt *xlsxPivotTableDefinition, opts *PivotTable
|
|||
Name: dataFieldsName[idx],
|
||||
Fld: dataField,
|
||||
Subtotal: dataFieldsSubtotals[idx],
|
||||
NumFmtID: dataFieldsNumFmtID[idx],
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -687,6 +695,22 @@ func (f *File) getPivotTableFieldName(name string, fields []PivotTableField) str
|
|||
return ""
|
||||
}
|
||||
|
||||
// getPivotTableFieldsNumFmtID prepare fields number format ID by given pivot
|
||||
// table fields.
|
||||
func (f *File) getPivotTableFieldsNumFmtID(fields []PivotTableField) []int {
|
||||
field := make([]int, len(fields))
|
||||
for idx, fld := range fields {
|
||||
if _, ok := builtInNumFmt[fld.NumFmt]; ok {
|
||||
field[idx] = fld.NumFmt
|
||||
continue
|
||||
}
|
||||
if (27 <= fld.NumFmt && fld.NumFmt <= 36) || (50 <= fld.NumFmt && fld.NumFmt <= 81) {
|
||||
field[idx] = fld.NumFmt
|
||||
}
|
||||
}
|
||||
return field
|
||||
}
|
||||
|
||||
// getPivotTableFieldOptions return options for specific field by given field name.
|
||||
func (f *File) getPivotTableFieldOptions(name string, fields []PivotTableField) (options PivotTableField, ok bool) {
|
||||
for _, field := range fields {
|
||||
|
@ -891,6 +915,7 @@ func (f *File) extractPivotTableFields(order []string, pt *xlsxPivotTableDefinit
|
|||
Data: order[field.Fld],
|
||||
Name: field.Name,
|
||||
Subtotal: cases.Title(language.English).String(field.Subtotal),
|
||||
NumFmt: field.NumFmtID,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ func TestPivotTable(t *testing.T) {
|
|||
Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
|
||||
Filter: []PivotTableField{{Data: "Region"}},
|
||||
Columns: []PivotTableField{{Data: "Type", DefaultSubtotal: true}},
|
||||
Data: []PivotTableField{{Data: "Sales", Subtotal: "Sum", Name: "Summarize by Sum"}},
|
||||
Data: []PivotTableField{{Data: "Sales", Subtotal: "Sum", Name: "Summarize by Sum", NumFmt: 38}},
|
||||
RowGrandTotals: true,
|
||||
ColGrandTotals: true,
|
||||
ShowDrill: true,
|
||||
|
@ -131,7 +131,7 @@ func TestPivotTable(t *testing.T) {
|
|||
PivotTableRange: "Sheet2!A1:AN17",
|
||||
Rows: []PivotTableField{{Data: "Month"}},
|
||||
Columns: []PivotTableField{{Data: "Region", DefaultSubtotal: true}, {Data: "Type", DefaultSubtotal: true}, {Data: "Year"}},
|
||||
Data: []PivotTableField{{Data: "Sales", Subtotal: "Min", Name: "Summarize by Min"}},
|
||||
Data: []PivotTableField{{Data: "Sales", Subtotal: "Min", Name: "Summarize by Min", NumFmt: 32}},
|
||||
RowGrandTotals: true,
|
||||
ColGrandTotals: true,
|
||||
ShowDrill: true,
|
||||
|
@ -151,7 +151,7 @@ func TestPivotTable(t *testing.T) {
|
|||
PivotTableRange: "Sheet2!A20:AR60",
|
||||
Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Type"}},
|
||||
Columns: []PivotTableField{{Data: "Region", DefaultSubtotal: true}, {Data: "Year"}},
|
||||
Data: []PivotTableField{{Data: "Sales", Subtotal: "Product", Name: "Summarize by Product"}},
|
||||
Data: []PivotTableField{{Data: "Sales", Subtotal: "Product", Name: "Summarize by Product", NumFmt: 32}},
|
||||
RowGrandTotals: true,
|
||||
ColGrandTotals: true,
|
||||
ShowDrill: true,
|
||||
|
@ -171,7 +171,7 @@ func TestPivotTable(t *testing.T) {
|
|||
PivotTableRange: "Sheet2!A65:AJ100",
|
||||
Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
|
||||
Columns: []PivotTableField{{Data: "Region", DefaultSubtotal: true}, {Data: "Type"}},
|
||||
Data: []PivotTableField{{Data: "Sales", Subtotal: "Sum", Name: "Sum of Sales"}, {Data: "Sales", Subtotal: "Average", Name: "Average of Sales"}},
|
||||
Data: []PivotTableField{{Data: "Sales", Subtotal: "Sum", Name: "Sum of Sales", NumFmt: -1}, {Data: "Sales", Subtotal: "Average", Name: "Average of Sales", NumFmt: 38}},
|
||||
RowGrandTotals: true,
|
||||
ColGrandTotals: true,
|
||||
ShowDrill: true,
|
||||
|
|
5
sheet.go
5
sheet.go
|
@ -773,6 +773,11 @@ func (f *File) SetSheetVisible(sheet string, visible bool, veryHidden ...bool) e
|
|||
return err
|
||||
}
|
||||
tabSelected := false
|
||||
if ws.SheetViews == nil {
|
||||
ws.SheetViews = &xlsxSheetViews{
|
||||
SheetView: []xlsxSheetView{{WorkbookViewID: 0}},
|
||||
}
|
||||
}
|
||||
if len(ws.SheetViews.SheetView) > 0 {
|
||||
tabSelected = ws.SheetViews.SheetView[0].TabSelected
|
||||
}
|
||||
|
|
|
@ -570,6 +570,18 @@ func TestSetSheetVisible(t *testing.T) {
|
|||
f.WorkBook = nil
|
||||
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
|
||||
assert.EqualError(t, f.SetSheetVisible("Sheet1", false), "XML syntax error on line 1: invalid UTF-8")
|
||||
|
||||
// Test set sheet visible with empty sheet views
|
||||
f = NewFile()
|
||||
_, err := f.NewSheet("Sheet2")
|
||||
assert.NoError(t, err)
|
||||
ws, ok := f.Sheet.Load("xl/worksheets/sheet2.xml")
|
||||
assert.True(t, ok)
|
||||
ws.(*xlsxWorksheet).SheetViews = nil
|
||||
assert.NoError(t, f.SetSheetVisible("Sheet2", false))
|
||||
visible, err := f.GetSheetVisible("Sheet2")
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, visible)
|
||||
}
|
||||
|
||||
func TestGetSheetVisible(t *testing.T) {
|
||||
|
|
43
styles.go
43
styles.go
|
@ -1376,22 +1376,33 @@ var (
|
|||
}
|
||||
)
|
||||
|
||||
// colorChoice returns a hex color code from the actual color values.
|
||||
func (clr *decodeCTColor) colorChoice() *string {
|
||||
if clr.SrgbClr != nil {
|
||||
return clr.SrgbClr.Val
|
||||
}
|
||||
if clr.SysClr != nil {
|
||||
return &clr.SysClr.LastClr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetBaseColor returns the preferred hex color code by giving hex color code,
|
||||
// indexed color, and theme color.
|
||||
func (f *File) GetBaseColor(hexColor string, indexedColor int, themeColor *int) string {
|
||||
if f.Theme != nil && themeColor != nil {
|
||||
clrScheme := f.Theme.ThemeElements.ClrScheme
|
||||
if val, ok := map[int]*string{
|
||||
0: &clrScheme.Lt1.SysClr.LastClr,
|
||||
1: &clrScheme.Dk1.SysClr.LastClr,
|
||||
2: clrScheme.Lt2.SrgbClr.Val,
|
||||
3: clrScheme.Dk2.SrgbClr.Val,
|
||||
4: clrScheme.Accent1.SrgbClr.Val,
|
||||
5: clrScheme.Accent2.SrgbClr.Val,
|
||||
6: clrScheme.Accent3.SrgbClr.Val,
|
||||
7: clrScheme.Accent4.SrgbClr.Val,
|
||||
8: clrScheme.Accent5.SrgbClr.Val,
|
||||
9: clrScheme.Accent6.SrgbClr.Val,
|
||||
0: clrScheme.Lt1.colorChoice(),
|
||||
1: clrScheme.Dk1.colorChoice(),
|
||||
2: clrScheme.Lt2.colorChoice(),
|
||||
3: clrScheme.Dk2.colorChoice(),
|
||||
4: clrScheme.Accent1.colorChoice(),
|
||||
5: clrScheme.Accent2.colorChoice(),
|
||||
6: clrScheme.Accent3.colorChoice(),
|
||||
7: clrScheme.Accent4.colorChoice(),
|
||||
8: clrScheme.Accent5.colorChoice(),
|
||||
9: clrScheme.Accent6.colorChoice(),
|
||||
}[*themeColor]; ok && val != nil {
|
||||
return *val
|
||||
}
|
||||
|
@ -1891,27 +1902,25 @@ func (f *File) newFont(style *Style) (*xlsxFont, error) {
|
|||
|
||||
// getNumFmtID provides a function to get number format code ID.
|
||||
// If given number format code does not exist, will return -1.
|
||||
func getNumFmtID(styleSheet *xlsxStyleSheet, style *Style) (numFmtID int) {
|
||||
numFmtID = -1
|
||||
func getNumFmtID(styleSheet *xlsxStyleSheet, style *Style) int {
|
||||
numFmtID := -1
|
||||
if _, ok := builtInNumFmt[style.NumFmt]; ok {
|
||||
return style.NumFmt
|
||||
}
|
||||
if (27 <= style.NumFmt && style.NumFmt <= 36) || (50 <= style.NumFmt && style.NumFmt <= 81) {
|
||||
numFmtID = style.NumFmt
|
||||
return
|
||||
return style.NumFmt
|
||||
}
|
||||
if fmtCode, ok := currencyNumFmt[style.NumFmt]; ok {
|
||||
numFmtID = style.NumFmt
|
||||
if styleSheet.NumFmts != nil {
|
||||
for _, numFmt := range styleSheet.NumFmts.NumFmt {
|
||||
if numFmt.FormatCode == fmtCode {
|
||||
numFmtID = numFmt.NumFmtID
|
||||
return
|
||||
return numFmt.NumFmtID
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
return numFmtID
|
||||
}
|
||||
|
||||
// newNumFmt provides a function to check if number format code in the range
|
||||
|
|
|
@ -613,6 +613,8 @@ func TestGetThemeColor(t *testing.T) {
|
|||
assert.Equal(t, "FFFFFF", f.getThemeColor(&xlsxColor{RGB: "FFFFFF"}))
|
||||
assert.Equal(t, "FF8080", f.getThemeColor(&xlsxColor{Indexed: 2, Tint: 0.5}))
|
||||
assert.Empty(t, f.getThemeColor(&xlsxColor{Indexed: len(IndexedColorMapping), Tint: 0.5}))
|
||||
clr := &decodeCTColor{}
|
||||
assert.Nil(t, clr.colorChoice())
|
||||
}
|
||||
|
||||
func TestGetStyle(t *testing.T) {
|
||||
|
|
|
@ -273,7 +273,7 @@ type xlsxDataField struct {
|
|||
ShowDataAs string `xml:"showDataAs,attr,omitempty"`
|
||||
BaseField int `xml:"baseField,attr,omitempty"`
|
||||
BaseItem int64 `xml:"baseItem,attr,omitempty"`
|
||||
NumFmtID string `xml:"numFmtId,attr,omitempty"`
|
||||
NumFmtID int `xml:"numFmtId,attr,omitempty"`
|
||||
ExtLst *xlsxExtLst `xml:"extLst"`
|
||||
}
|
||||
|
||||
|
|
|
@ -148,7 +148,7 @@ type xlsxEffectStyleLst struct {
|
|||
EffectStyleLst string `xml:",innerxml"`
|
||||
}
|
||||
|
||||
// xlsxBgFillStyleLst element defines a list of background fills that are
|
||||
// xlsxBgFillStyleLst element defines a list of background fills that are
|
||||
// used within a theme. The background fills consist of three fills, arranged
|
||||
// in order from subtle to moderate to intense.
|
||||
type xlsxBgFillStyleLst struct {
|
||||
|
|
Loading…
Reference in New Issue