Compare commits
64 Commits
Author | SHA1 | Date |
---|---|---|
Eng Zer Jun | c93618856a | |
xuri | 5f446f25f0 | |
Ilia Mirkin | 30d3561d0e | |
Ilia Mirkin | d2be5cdf8e | |
Ilia Mirkin | b7375bc6d4 | |
xuri | 0d5d1c53b2 | |
xuri | af190c7fdc | |
wushiling50 | d1937a0cde | |
xuri | f1d1a5dc2b | |
Jian Yu, Chen | b23e5a26df | |
liuwangchao | bebb802069 | |
xuri | 41c7dd30ce | |
ArcholSevier | 02189fb016 | |
xuri | ad8541790d | |
xuri | 8c0ef7f90d | |
Ben Smith | aca04ecf57 | |
Zhang Zhipeng | 0447cb22c8 | |
xuri | 29366fd126 | |
centurion-hub | 8f87131608 | |
wanghaochen2024 | 9c460ffe6c | |
zhangyimingdatiancai | c805be1f6f | |
xuri | 9a38657515 | |
xuri | d21b598235 | |
xuri | 30c4cd70e0 | |
xuri | d81b4c8661 | |
xuri | 4dd34477f7 | |
pjh591029530 | 68a1704900 | |
xuri | 9c278365f2 | |
wxy | 307e533061 | |
Patrick Wang | 431c31029e | |
xuri | 53b65150ce | |
ShowerBandV | 7999a492a4 | |
Aybek | b18b48099b | |
Vovka Morkovka | 4e6457accd | |
wangsongyan | f04aa8dd31 | |
联盟少侠 | 1a99dd4a23 | |
xuri | c349313850 | |
xiaokui | 08d25006f9 | |
xuri | 0c3dfb1605 | |
xuri | 42ad4d6959 | |
nna | 5f583549f4 | |
xuri | a64efca31f | |
barlevd | 781c38481d | |
xuri | 7715c1462a | |
xuri | 055349d8a6 | |
jianxinhou | f8487a68a8 | |
Nima | 3e636ae7b2 | |
xuri | 5f8a5b8690 | |
xuri | 5dc22e874b | |
xuri | 9999221450 | |
yunkeweb | ffad7aecb5 | |
yangyile-yyle88 | 5e500f5e5d | |
Matthew Sackman | 838232fd27 | |
xuri | 703b73779c | |
realzuojianxiang | 5975d87f7e | |
vic | 9e884c798b | |
hu5ky | 4eb088cf73 | |
yeahyear | 585ebff5b7 | |
Evan lu | 4ed493819a | |
xuri | f20bbd1f1d | |
Paolo Barbolini | 963a058535 | |
陈王 | 9d4c2e60f6 | |
岳晨旭 | 7b4da3906d | |
helloWorld | bb603b37d0 |
|
@ -5,8 +5,8 @@ jobs:
|
|||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.18.x, 1.19.x, 1.20.x, '>=1.21.1', 1.22.x]
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
go-version: [1.18.x, 1.19.x, 1.20.x, '>=1.21.1', 1.22.x, 1.23.x]
|
||||
os: [ubuntu-latest, macos-13, windows-latest]
|
||||
targetplatform: [x86, x64]
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
@ -29,7 +29,7 @@ jobs:
|
|||
run: go build -v .
|
||||
|
||||
- name: Test
|
||||
run: env GO111MODULE=on go test -v -timeout 30m -race ./... -coverprofile=coverage.txt -covermode=atomic
|
||||
run: env GO111MODULE=on go test -v -timeout 30m -race ./... -coverprofile='coverage.txt' -covermode=atomic
|
||||
|
||||
- name: Codecov
|
||||
uses: codecov/codecov-action@v4
|
||||
|
|
138
adjust.go
138
adjust.go
|
@ -201,13 +201,13 @@ func (f *File) adjustRowDimensions(sheet string, ws *xlsxWorksheet, row, offset
|
|||
return nil
|
||||
}
|
||||
lastRow := &ws.SheetData.Row[totalRows-1]
|
||||
if newRow := *lastRow.R + offset; *lastRow.R >= row && newRow > 0 && newRow > TotalRows {
|
||||
if newRow := lastRow.R + offset; lastRow.R >= row && newRow > 0 && newRow > TotalRows {
|
||||
return ErrMaxRows
|
||||
}
|
||||
numOfRows := len(ws.SheetData.Row)
|
||||
for i := 0; i < numOfRows; i++ {
|
||||
r := &ws.SheetData.Row[i]
|
||||
if newRow := *r.R + offset; *r.R >= row && newRow > 0 {
|
||||
if newRow := r.R + offset; r.R >= row && newRow > 0 {
|
||||
r.adjustSingleRowDimensions(offset)
|
||||
}
|
||||
if err := f.adjustSingleRowFormulas(sheet, sheet, r, row, offset, false); err != nil {
|
||||
|
@ -219,10 +219,10 @@ func (f *File) adjustRowDimensions(sheet string, ws *xlsxWorksheet, row, offset
|
|||
|
||||
// adjustSingleRowDimensions provides a function to adjust single row dimensions.
|
||||
func (r *xlsxRow) adjustSingleRowDimensions(offset int) {
|
||||
r.R = intPtr(*r.R + offset)
|
||||
r.R += offset
|
||||
for i, col := range r.C {
|
||||
colName, _, _ := SplitCellName(col.R)
|
||||
r.C[i].R, _ = JoinCellName(colName, *r.R)
|
||||
r.C[i].R, _ = JoinCellName(colName, r.R)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -237,38 +237,44 @@ func (f *File) adjustSingleRowFormulas(sheet, sheetN string, r *xlsxRow, num, of
|
|||
}
|
||||
|
||||
// adjustCellRef provides a function to adjust cell reference.
|
||||
func (f *File) adjustCellRef(ref string, dir adjustDirection, num, offset int) (string, bool, error) {
|
||||
if !strings.Contains(ref, ":") {
|
||||
ref += ":" + ref
|
||||
func (f *File) adjustCellRef(cellRef string, dir adjustDirection, num, offset int) (string, error) {
|
||||
var SQRef []string
|
||||
applyOffset := func(coordinates []int, idx1, idx2, maxVal int) []int {
|
||||
if coordinates[idx1] >= num {
|
||||
coordinates[idx1] += offset
|
||||
}
|
||||
if coordinates[idx2] >= num {
|
||||
if coordinates[idx2] += offset; coordinates[idx2] > maxVal {
|
||||
coordinates[idx2] = maxVal
|
||||
}
|
||||
}
|
||||
return coordinates
|
||||
}
|
||||
var delete bool
|
||||
coordinates, err := rangeRefToCoordinates(ref)
|
||||
if err != nil {
|
||||
return ref, delete, err
|
||||
for _, ref := range strings.Split(cellRef, " ") {
|
||||
if !strings.Contains(ref, ":") {
|
||||
ref += ":" + ref
|
||||
}
|
||||
coordinates, err := rangeRefToCoordinates(ref)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if dir == columns {
|
||||
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] && num == coordinates[1] {
|
||||
continue
|
||||
}
|
||||
coordinates = applyOffset(coordinates, 1, 3, TotalRows)
|
||||
}
|
||||
if ref, err = coordinatesToRangeRef(coordinates); err != nil {
|
||||
return "", err
|
||||
}
|
||||
SQRef = append(SQRef, ref)
|
||||
}
|
||||
if dir == columns {
|
||||
if offset < 0 && coordinates[0] == coordinates[2] {
|
||||
delete = true
|
||||
}
|
||||
if coordinates[0] >= num {
|
||||
coordinates[0] += offset
|
||||
}
|
||||
if coordinates[2] >= num {
|
||||
coordinates[2] += offset
|
||||
}
|
||||
} else {
|
||||
if offset < 0 && coordinates[1] == coordinates[3] {
|
||||
delete = true
|
||||
}
|
||||
if coordinates[1] >= num {
|
||||
coordinates[1] += offset
|
||||
}
|
||||
if coordinates[3] >= num {
|
||||
coordinates[3] += offset
|
||||
}
|
||||
}
|
||||
ref, err = f.coordinatesToRangeRef(coordinates)
|
||||
return ref, delete, err
|
||||
return strings.Join(SQRef, " "), nil
|
||||
}
|
||||
|
||||
// adjustFormula provides a function to adjust formula reference and shared
|
||||
|
@ -284,7 +290,7 @@ func (f *File) adjustFormula(sheet, sheetN string, cell *xlsxC, dir adjustDirect
|
|||
return nil
|
||||
}
|
||||
if cell.F.Ref != "" && sheet == sheetN {
|
||||
if cell.F.Ref, _, err = f.adjustCellRef(cell.F.Ref, dir, num, offset); err != nil {
|
||||
if cell.F.Ref, err = f.adjustCellRef(cell.F.Ref, dir, num, offset); err != nil {
|
||||
return err
|
||||
}
|
||||
if si && cell.F.Si != nil {
|
||||
|
@ -320,7 +326,9 @@ func adjustFormulaColumnName(name, operand string, abs, keepRelative bool, dir a
|
|||
return "", operand, false, err
|
||||
}
|
||||
if dir == columns && col >= num {
|
||||
col += offset
|
||||
if col += offset; col < 1 {
|
||||
col = 1
|
||||
}
|
||||
colName, err := ColumnNumberToName(col)
|
||||
return "", operand + colName, false, err
|
||||
}
|
||||
|
@ -334,8 +342,10 @@ func adjustFormulaRowNumber(name, operand string, abs, keepRelative bool, dir ad
|
|||
}
|
||||
row, _ := strconv.Atoi(name)
|
||||
if dir == rows && row >= num {
|
||||
row += offset
|
||||
if row <= 0 || row > TotalRows {
|
||||
if row += offset; row < 1 {
|
||||
row = 1
|
||||
}
|
||||
if row > TotalRows {
|
||||
return "", operand + name, false, ErrMaxRows
|
||||
}
|
||||
return "", operand + strconv.Itoa(row), false, nil
|
||||
|
@ -437,12 +447,8 @@ func (f *File) adjustFormulaRef(sheet, sheetN, formula string, keepRelative bool
|
|||
val += operand
|
||||
continue
|
||||
}
|
||||
if isFunctionStartToken(token) {
|
||||
val += token.TValue + string(efp.ParenOpen)
|
||||
continue
|
||||
}
|
||||
if isFunctionStopToken(token) {
|
||||
val += token.TValue + string(efp.ParenClose)
|
||||
if paren := transformParenthesesToken(token); paren != "" {
|
||||
val += transformParenthesesToken(token)
|
||||
continue
|
||||
}
|
||||
if token.TType == efp.TokenTypeOperand && token.TSubType == efp.TokenSubTypeText {
|
||||
|
@ -454,6 +460,18 @@ func (f *File) adjustFormulaRef(sheet, sheetN, formula string, keepRelative bool
|
|||
return val, nil
|
||||
}
|
||||
|
||||
// transformParenthesesToken returns formula part with parentheses by given
|
||||
// token.
|
||||
func transformParenthesesToken(token efp.Token) string {
|
||||
if isFunctionStartToken(token) || isBeginParenthesesToken(token) {
|
||||
return token.TValue + string(efp.ParenOpen)
|
||||
}
|
||||
if isFunctionStopToken(token) || isEndParenthesesToken(token) {
|
||||
return token.TValue + string(efp.ParenClose)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// adjustRangeSheetName returns replaced range reference by given source and
|
||||
// target sheet name.
|
||||
func adjustRangeSheetName(rng, source, target string) string {
|
||||
|
@ -542,12 +560,8 @@ func transformArrayFormula(tokens []efp.Token, afs []arrayFormulaOperandToken) s
|
|||
if skip {
|
||||
continue
|
||||
}
|
||||
if isFunctionStartToken(token) {
|
||||
val += token.TValue + string(efp.ParenOpen)
|
||||
continue
|
||||
}
|
||||
if isFunctionStopToken(token) {
|
||||
val += token.TValue + string(efp.ParenClose)
|
||||
if paren := transformParenthesesToken(token); paren != "" {
|
||||
val += transformParenthesesToken(token)
|
||||
continue
|
||||
}
|
||||
if token.TType == efp.TokenTypeOperand && token.TSubType == efp.TokenSubTypeText {
|
||||
|
@ -662,7 +676,7 @@ func (f *File) adjustTable(ws *xlsxWorksheet, sheet string, dir adjustDirection,
|
|||
idx--
|
||||
continue
|
||||
}
|
||||
t.Ref, _ = f.coordinatesToRangeRef([]int{x1, y1, x2, y2})
|
||||
t.Ref, _ = coordinatesToRangeRef([]int{x1, y1, x2, y2})
|
||||
if t.AutoFilter != nil {
|
||||
t.AutoFilter.Ref = t.Ref
|
||||
}
|
||||
|
@ -692,7 +706,7 @@ func (f *File) adjustAutoFilter(ws *xlsxWorksheet, sheet string, dir adjustDirec
|
|||
ws.AutoFilter = nil
|
||||
for rowIdx := range ws.SheetData.Row {
|
||||
rowData := &ws.SheetData.Row[rowIdx]
|
||||
if rowData.R != nil && *rowData.R > y1 && *rowData.R <= y2 {
|
||||
if rowData.R > y1 && rowData.R <= y2 {
|
||||
rowData.Hidden = false
|
||||
}
|
||||
}
|
||||
|
@ -702,7 +716,7 @@ func (f *File) adjustAutoFilter(ws *xlsxWorksheet, sheet string, dir adjustDirec
|
|||
coordinates = f.adjustAutoFilterHelper(dir, coordinates, num, offset)
|
||||
x1, y1, x2, y2 = coordinates[0], coordinates[1], coordinates[2], coordinates[3]
|
||||
|
||||
ws.AutoFilter.Ref, err = f.coordinatesToRangeRef([]int{x1, y1, x2, y2})
|
||||
ws.AutoFilter.Ref, err = coordinatesToRangeRef([]int{x1, y1, x2, y2})
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -769,7 +783,7 @@ func (f *File) adjustMergeCells(ws *xlsxWorksheet, sheet string, dir adjustDirec
|
|||
continue
|
||||
}
|
||||
mergedCells.rect = []int{x1, y1, x2, y2}
|
||||
if mergedCells.Ref, err = f.coordinatesToRangeRef([]int{x1, y1, x2, y2}); err != nil {
|
||||
if mergedCells.Ref, err = coordinatesToRangeRef([]int{x1, y1, x2, y2}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -928,11 +942,11 @@ func (f *File) adjustConditionalFormats(ws *xlsxWorksheet, sheet string, dir adj
|
|||
if cf == nil {
|
||||
continue
|
||||
}
|
||||
ref, del, err := f.adjustCellRef(cf.SQRef, dir, num, offset)
|
||||
ref, err := f.adjustCellRef(cf.SQRef, dir, num, offset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if del {
|
||||
if ref == "" {
|
||||
ws.ConditionalFormatting = append(ws.ConditionalFormatting[:i],
|
||||
ws.ConditionalFormatting[i+1:]...)
|
||||
i--
|
||||
|
@ -963,11 +977,11 @@ func (f *File) adjustDataValidations(ws *xlsxWorksheet, sheet string, dir adjust
|
|||
continue
|
||||
}
|
||||
if sheet == sheetN {
|
||||
ref, del, err := f.adjustCellRef(dv.Sqref, dir, num, offset)
|
||||
ref, err := f.adjustCellRef(dv.Sqref, dir, num, offset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if del {
|
||||
if ref == "" {
|
||||
worksheet.DataValidations.DataValidation = append(worksheet.DataValidations.DataValidation[:i],
|
||||
worksheet.DataValidations.DataValidation[i+1:]...)
|
||||
i--
|
||||
|
@ -975,15 +989,15 @@ func (f *File) adjustDataValidations(ws *xlsxWorksheet, sheet string, dir adjust
|
|||
}
|
||||
worksheet.DataValidations.DataValidation[i].Sqref = ref
|
||||
}
|
||||
if worksheet.DataValidations.DataValidation[i].Formula1 != nil {
|
||||
formula := unescapeDataValidationFormula(worksheet.DataValidations.DataValidation[i].Formula1.Content)
|
||||
if worksheet.DataValidations.DataValidation[i].Formula1.isFormula() {
|
||||
formula := formulaUnescaper.Replace(worksheet.DataValidations.DataValidation[i].Formula1.Content)
|
||||
if formula, err = f.adjustFormulaRef(sheet, sheetN, formula, false, dir, num, offset); err != nil {
|
||||
return err
|
||||
}
|
||||
worksheet.DataValidations.DataValidation[i].Formula1 = &xlsxInnerXML{Content: formulaEscaper.Replace(formula)}
|
||||
}
|
||||
if worksheet.DataValidations.DataValidation[i].Formula2 != nil {
|
||||
formula := unescapeDataValidationFormula(worksheet.DataValidations.DataValidation[i].Formula2.Content)
|
||||
if worksheet.DataValidations.DataValidation[i].Formula2.isFormula() {
|
||||
formula := formulaUnescaper.Replace(worksheet.DataValidations.DataValidation[i].Formula2.Content)
|
||||
if formula, err = f.adjustFormulaRef(sheet, sheetN, formula, false, dir, num, offset); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"encoding/xml"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
_ "image/jpeg"
|
||||
|
@ -289,7 +290,7 @@ func TestAdjustAutoFilter(t *testing.T) {
|
|||
f := NewFile()
|
||||
assert.NoError(t, f.adjustAutoFilter(&xlsxWorksheet{
|
||||
SheetData: xlsxSheetData{
|
||||
Row: []xlsxRow{{Hidden: true, R: intPtr(2)}},
|
||||
Row: []xlsxRow{{Hidden: true, R: 2}},
|
||||
},
|
||||
AutoFilter: &xlsxAutoFilter{
|
||||
Ref: "A1:A3",
|
||||
|
@ -463,6 +464,14 @@ func TestAdjustCols(t *testing.T) {
|
|||
|
||||
assert.NoError(t, f.InsertCols("Sheet1", "A", 2))
|
||||
assert.Nil(t, ws.(*xlsxWorksheet).Cols)
|
||||
|
||||
f = NewFile()
|
||||
assert.NoError(t, f.SetCellFormula("Sheet1", "A2", "(1-0.5)/2"))
|
||||
assert.NoError(t, f.InsertCols("Sheet1", "A", 1))
|
||||
formula, err := f.GetCellFormula("Sheet1", "B2")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "(1-0.5)/2", formula)
|
||||
assert.NoError(t, f.Close())
|
||||
}
|
||||
|
||||
func TestAdjustColDimensions(t *testing.T) {
|
||||
|
@ -991,7 +1000,7 @@ func TestAdjustConditionalFormats(t *testing.T) {
|
|||
{
|
||||
Type: "cell",
|
||||
Criteria: "greater than",
|
||||
Format: formatID,
|
||||
Format: &formatID,
|
||||
Value: "0",
|
||||
},
|
||||
}
|
||||
|
@ -1011,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) {
|
||||
|
@ -1059,6 +1119,16 @@ func TestAdjustDataValidations(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.Equal(t, "\"A<,B>,C\",D\t,E',F\"", dvs[2].Formula1)
|
||||
|
||||
// Test adjust data validation with multiple cell range
|
||||
dv = NewDataValidation(true)
|
||||
dv.Sqref = "G1:G3 H1:H3 A3:A1048576"
|
||||
assert.NoError(t, dv.SetDropList([]string{"1", "2", "3"}))
|
||||
assert.NoError(t, f.AddDataValidation("Sheet1", dv))
|
||||
assert.NoError(t, f.InsertRows("Sheet1", 2, 1))
|
||||
dvs, err = f.GetDataValidations("Sheet1")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "G1:G4 H1:H4 A4:A1048576", dvs[3].Sqref)
|
||||
|
||||
dv = NewDataValidation(true)
|
||||
dv.Sqref = "C5:D6"
|
||||
assert.NoError(t, dv.SetRange("Sheet1!A1048576", "Sheet1!XFD1", DataValidationTypeWhole, DataValidationOperatorBetween))
|
||||
|
@ -1081,6 +1151,25 @@ func TestAdjustDataValidations(t *testing.T) {
|
|||
f.Sheet.Delete("xl/worksheets/sheet1.xml")
|
||||
f.Pkg.Store("xl/worksheets/sheet1.xml", MacintoshCyrillicCharset)
|
||||
assert.EqualError(t, f.adjustDataValidations(nil, "Sheet1", columns, 0, 0, 1), "XML syntax error on line 1: invalid UTF-8")
|
||||
|
||||
t.Run("for_escaped_data_validation_rules_formula", func(t *testing.T) {
|
||||
f := NewFile()
|
||||
_, err := f.NewSheet("Sheet2")
|
||||
assert.NoError(t, err)
|
||||
dv := NewDataValidation(true)
|
||||
dv.Sqref = "A1"
|
||||
assert.NoError(t, dv.SetDropList([]string{"option1", strings.Repeat("\"", 4)}))
|
||||
ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
|
||||
assert.True(t, ok)
|
||||
assert.NoError(t, f.AddDataValidation("Sheet1", dv))
|
||||
// The double quote symbol in none formula data validation rules will be escaped in the Kingsoft WPS Office
|
||||
formula := strings.ReplaceAll(fmt.Sprintf("\"option1, %s", strings.Repeat("\"", 9)), "\"", """)
|
||||
ws.(*xlsxWorksheet).DataValidations.DataValidation[0].Formula1.Content = formula
|
||||
assert.NoError(t, f.RemoveCol("Sheet2", "A"))
|
||||
dvs, err := f.GetDataValidations("Sheet1")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, formula, dvs[0].Formula1)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAdjustDrawings(t *testing.T) {
|
||||
|
|
187
calc.go
187
calc.go
|
@ -478,6 +478,7 @@ type formulaFuncs struct {
|
|||
// DISC
|
||||
// DMAX
|
||||
// DMIN
|
||||
// DOLLAR
|
||||
// DOLLARDE
|
||||
// DOLLARFR
|
||||
// DPRODUCT
|
||||
|
@ -831,8 +832,8 @@ func (f *File) CalcCellValue(sheet, cell string, opts ...Options) (result string
|
|||
if !rawCellValue {
|
||||
styleIdx, _ = f.GetCellStyle(sheet, cell)
|
||||
}
|
||||
result = token.Value()
|
||||
if isNum, precision, decimal := isNumeric(result); isNum && !rawCellValue {
|
||||
if token.Type == ArgNumber && !token.Boolean {
|
||||
_, precision, decimal := isNumeric(token.Value())
|
||||
if precision > 15 {
|
||||
result, err = f.formattedValue(&xlsxC{S: styleIdx, V: strings.ToUpper(strconv.FormatFloat(decimal, 'G', 15, 64))}, rawCellValue, CellTypeNumber)
|
||||
return
|
||||
|
@ -840,7 +841,9 @@ func (f *File) CalcCellValue(sheet, cell string, opts ...Options) (result string
|
|||
if !strings.HasPrefix(result, "0") {
|
||||
result, err = f.formattedValue(&xlsxC{S: styleIdx, V: strings.ToUpper(strconv.FormatFloat(decimal, 'f', -1, 64))}, rawCellValue, CellTypeNumber)
|
||||
}
|
||||
return
|
||||
}
|
||||
result, err = f.formattedValue(&xlsxC{S: styleIdx, V: token.Value()}, rawCellValue, CellTypeInlineString)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -4281,7 +4284,7 @@ func (fn *formulaFuncs) EXP(argsList *list.List) formulaArg {
|
|||
if number.Type == ArgError {
|
||||
return number
|
||||
}
|
||||
return newStringFormulaArg(strings.ToUpper(fmt.Sprintf("%g", math.Exp(number.Number))))
|
||||
return newNumberFormulaArg(math.Exp(number.Number))
|
||||
}
|
||||
|
||||
// fact returns the factorial of a supplied number.
|
||||
|
@ -4359,7 +4362,7 @@ func (fn *formulaFuncs) FLOOR(argsList *list.List) formulaArg {
|
|||
val--
|
||||
}
|
||||
}
|
||||
return newStringFormulaArg(strings.ToUpper(fmt.Sprintf("%g", val*significance.Number)))
|
||||
return newNumberFormulaArg(val * significance.Number)
|
||||
}
|
||||
|
||||
// FLOORdotMATH function rounds a supplied number down to a supplied multiple
|
||||
|
@ -11570,12 +11573,10 @@ func (fn *formulaFuncs) ISNA(argsList *list.List) formulaArg {
|
|||
if argsList.Len() != 1 {
|
||||
return newErrorFormulaArg(formulaErrorVALUE, "ISNA requires 1 argument")
|
||||
}
|
||||
token := argsList.Front().Value.(formulaArg)
|
||||
result := "FALSE"
|
||||
if token.Type == ArgError && token.String == formulaErrorNA {
|
||||
result = "TRUE"
|
||||
if token := argsList.Front().Value.(formulaArg); token.Type == ArgError && token.String == formulaErrorNA {
|
||||
return newBoolFormulaArg(true)
|
||||
}
|
||||
return newStringFormulaArg(result)
|
||||
return newBoolFormulaArg(false)
|
||||
}
|
||||
|
||||
// ISNONTEXT function tests if a supplied value is text. If not, the
|
||||
|
@ -11602,7 +11603,22 @@ func (fn *formulaFuncs) ISNUMBER(argsList *list.List) formulaArg {
|
|||
if argsList.Len() != 1 {
|
||||
return newErrorFormulaArg(formulaErrorVALUE, "ISNUMBER requires 1 argument")
|
||||
}
|
||||
if argsList.Front().Value.(formulaArg).Type == ArgNumber {
|
||||
arg := argsList.Front().Value.(formulaArg)
|
||||
if arg.Type == ArgMatrix {
|
||||
var mtx [][]formulaArg
|
||||
for _, row := range arg.Matrix {
|
||||
var array []formulaArg
|
||||
for _, val := range row {
|
||||
if val.Type == ArgNumber {
|
||||
array = append(array, newBoolFormulaArg(true))
|
||||
}
|
||||
array = append(array, newBoolFormulaArg(false))
|
||||
}
|
||||
mtx = append(mtx, array)
|
||||
}
|
||||
return newMatrixFormulaArg(mtx)
|
||||
}
|
||||
if arg.Type == ArgNumber {
|
||||
return newBoolFormulaArg(true)
|
||||
}
|
||||
return newBoolFormulaArg(false)
|
||||
|
@ -11951,11 +11967,14 @@ func (fn *formulaFuncs) OR(argsList *list.List) formulaArg {
|
|||
return newStringFormulaArg(strings.ToUpper(strconv.FormatBool(or)))
|
||||
}
|
||||
case ArgMatrix:
|
||||
// TODO
|
||||
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
|
||||
args := list.New()
|
||||
for _, arg := range token.ToList() {
|
||||
args.PushBack(arg)
|
||||
}
|
||||
return fn.OR(args)
|
||||
}
|
||||
}
|
||||
return newStringFormulaArg(strings.ToUpper(strconv.FormatBool(or)))
|
||||
return newBoolFormulaArg(or)
|
||||
}
|
||||
|
||||
// SWITCH function compares a number of supplied values to a supplied test
|
||||
|
@ -12063,7 +12082,7 @@ func (fn *formulaFuncs) DATE(argsList *list.List) formulaArg {
|
|||
return newErrorFormulaArg(formulaErrorVALUE, "DATE requires 3 number arguments")
|
||||
}
|
||||
d := makeDate(int(year.Number), time.Month(month.Number), int(day.Number))
|
||||
return newStringFormulaArg(timeFromExcelTime(daysBetween(excelMinTime1900.Unix(), d)+1, false).String())
|
||||
return newNumberFormulaArg(daysBetween(excelMinTime1900.Unix(), d) + 1)
|
||||
}
|
||||
|
||||
// calcDateDif is an implementation of the formula function DATEDIF,
|
||||
|
@ -13610,7 +13629,9 @@ func (fn *formulaFuncs) DBCS(argsList *list.List) formulaArg {
|
|||
if arg.Type == ArgError {
|
||||
return arg
|
||||
}
|
||||
if fn.f.options.CultureInfo == CultureNameZhCN {
|
||||
if fn.f.options.CultureInfo == CultureNameJaJP ||
|
||||
fn.f.options.CultureInfo == CultureNameZhCN ||
|
||||
fn.f.options.CultureInfo == CultureNameZhTW {
|
||||
var chars []string
|
||||
for _, r := range arg.Value() {
|
||||
code := r
|
||||
|
@ -13741,34 +13762,48 @@ func (fn *formulaFuncs) find(name string, argsList *list.List) formulaArg {
|
|||
if args.Type != ArgList {
|
||||
return args
|
||||
}
|
||||
findText := argsList.Front().Value.(formulaArg).Value()
|
||||
findTextArg := argsList.Front().Value.(formulaArg)
|
||||
withinText := argsList.Front().Next().Value.(formulaArg).Value()
|
||||
startNum := int(args.List[0].Number)
|
||||
if findText == "" {
|
||||
return newNumberFormulaArg(float64(startNum))
|
||||
}
|
||||
dbcs, search := name == "FINDB" || name == "SEARCHB", name == "SEARCH" || name == "SEARCHB"
|
||||
if search {
|
||||
findText, withinText = strings.ToUpper(findText), strings.ToUpper(withinText)
|
||||
}
|
||||
offset, ok := matchPattern(findText, withinText, dbcs, startNum)
|
||||
if !ok {
|
||||
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
|
||||
}
|
||||
result := offset
|
||||
if dbcs {
|
||||
var pre int
|
||||
for idx := range withinText {
|
||||
if pre > offset {
|
||||
break
|
||||
}
|
||||
if idx-pre > 1 {
|
||||
result++
|
||||
}
|
||||
pre = idx
|
||||
find := func(findText string) formulaArg {
|
||||
if findText == "" {
|
||||
return newNumberFormulaArg(float64(startNum))
|
||||
}
|
||||
if search {
|
||||
findText, withinText = strings.ToUpper(findText), strings.ToUpper(withinText)
|
||||
}
|
||||
offset, ok := matchPattern(findText, withinText, dbcs, startNum)
|
||||
if !ok {
|
||||
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
|
||||
}
|
||||
result := offset
|
||||
if dbcs {
|
||||
var pre int
|
||||
for idx := range withinText {
|
||||
if pre > offset {
|
||||
break
|
||||
}
|
||||
if idx-pre > 1 {
|
||||
result++
|
||||
}
|
||||
pre = idx
|
||||
}
|
||||
}
|
||||
return newNumberFormulaArg(float64(result))
|
||||
}
|
||||
return newNumberFormulaArg(float64(result))
|
||||
if findTextArg.Type == ArgMatrix {
|
||||
var mtx [][]formulaArg
|
||||
for _, row := range findTextArg.Matrix {
|
||||
var array []formulaArg
|
||||
for _, findText := range row {
|
||||
array = append(array, find(findText.Value()))
|
||||
}
|
||||
mtx = append(mtx, array)
|
||||
}
|
||||
return newMatrixFormulaArg(mtx)
|
||||
}
|
||||
return find(findTextArg.Value())
|
||||
}
|
||||
|
||||
// LEFT function returns a specified number of characters from the start of a
|
||||
|
@ -14427,7 +14462,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
|
||||
|
@ -14436,7 +14471,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
|
||||
}
|
||||
|
@ -16309,6 +16344,52 @@ func (fn *formulaFuncs) DISC(argsList *list.List) formulaArg {
|
|||
return fn.discIntrate("DISC", argsList)
|
||||
}
|
||||
|
||||
// DOLLAR function rounds a supplied number to a specified number of decimal
|
||||
// places and then converts this into a text string with a currency format. The
|
||||
// syntax of the function is:
|
||||
//
|
||||
// DOLLAR(number,[decimals])
|
||||
func (fn *formulaFuncs) DOLLAR(argsList *list.List) formulaArg {
|
||||
if argsList.Len() == 0 {
|
||||
return newErrorFormulaArg(formulaErrorVALUE, "DOLLAR requires at least 1 argument")
|
||||
}
|
||||
if argsList.Len() > 2 {
|
||||
return newErrorFormulaArg(formulaErrorVALUE, "DOLLAR requires 1 or 2 arguments")
|
||||
}
|
||||
numArg := argsList.Front().Value.(formulaArg)
|
||||
n := numArg.ToNumber()
|
||||
if n.Type != ArgNumber {
|
||||
return n
|
||||
}
|
||||
decimals, dot, value := 2, ".", numArg.Value()
|
||||
if argsList.Len() == 2 {
|
||||
d := argsList.Back().Value.(formulaArg).ToNumber()
|
||||
if d.Type != ArgNumber {
|
||||
return d
|
||||
}
|
||||
if d.Number < 0 {
|
||||
value = strconv.FormatFloat(fn.round(n.Number, d.Number, down), 'f', -1, 64)
|
||||
}
|
||||
if d.Number >= 128 {
|
||||
return newErrorFormulaArg(formulaErrorVALUE, "decimal value should be less than 128")
|
||||
}
|
||||
if decimals = int(d.Number); decimals < 0 {
|
||||
decimals, dot = 0, ""
|
||||
}
|
||||
}
|
||||
symbol := map[CultureName]string{
|
||||
CultureNameUnknown: "$",
|
||||
CultureNameEnUS: "$",
|
||||
CultureNameJaJP: "¥",
|
||||
CultureNameKoKR: "\u20a9",
|
||||
CultureNameZhCN: "¥",
|
||||
CultureNameZhTW: "NT$",
|
||||
}[fn.f.options.CultureInfo]
|
||||
numFmtCode := fmt.Sprintf("%s#,##0%s%s;(%s#,##0%s%s)",
|
||||
symbol, dot, strings.Repeat("0", decimals), symbol, dot, strings.Repeat("0", decimals))
|
||||
return newStringFormulaArg(format(value, numFmtCode, false, CellTypeNumber, nil))
|
||||
}
|
||||
|
||||
// DOLLARDE function converts a dollar value in fractional notation, into a
|
||||
// dollar value expressed as a decimal. The syntax of the function is:
|
||||
//
|
||||
|
@ -18165,28 +18246,26 @@ func (fn *formulaFuncs) prepareXArgs(values, dates formulaArg) (valuesArg, dates
|
|||
valuesArg = append(valuesArg, numArg.Number)
|
||||
continue
|
||||
}
|
||||
err = newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
|
||||
err = newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
|
||||
return
|
||||
}
|
||||
if len(valuesArg) < 2 {
|
||||
err = newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
|
||||
return
|
||||
}
|
||||
args, date := list.New(), 0.0
|
||||
date := 0.0
|
||||
for _, arg := range dates.ToList() {
|
||||
args.Init()
|
||||
args.PushBack(arg)
|
||||
dateValue := fn.DATEVALUE(args)
|
||||
if dateValue.Type != ArgNumber {
|
||||
err = newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
|
||||
return
|
||||
if arg.Type == ArgNumber {
|
||||
datesArg = append(datesArg, arg.Number)
|
||||
if arg.Number < date {
|
||||
err = newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
|
||||
return
|
||||
}
|
||||
date = arg.Number
|
||||
continue
|
||||
}
|
||||
if dateValue.Number < date {
|
||||
err = newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
|
||||
return
|
||||
}
|
||||
datesArg = append(datesArg, dateValue.Number)
|
||||
date = dateValue.Number
|
||||
err = newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
|
||||
return
|
||||
}
|
||||
if len(valuesArg) != len(datesArg) {
|
||||
err = newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
|
||||
|
|
95
calc_test.go
95
calc_test.go
|
@ -1451,8 +1451,9 @@ func TestCalcCellValue(t *testing.T) {
|
|||
"=ISNONTEXT(\"Excelize\")": "FALSE",
|
||||
"=ISNONTEXT(NA())": "TRUE",
|
||||
// ISNUMBER
|
||||
"=ISNUMBER(A1)": "TRUE",
|
||||
"=ISNUMBER(D1)": "FALSE",
|
||||
"=ISNUMBER(A1)": "TRUE",
|
||||
"=ISNUMBER(D1)": "FALSE",
|
||||
"=ISNUMBER(A1:B1)": "TRUE",
|
||||
// ISODD
|
||||
"=ISODD(A1)": "TRUE",
|
||||
"=ISODD(A2)": "FALSE",
|
||||
|
@ -1526,6 +1527,7 @@ func TestCalcCellValue(t *testing.T) {
|
|||
"=OR(1=2,2=3)": "FALSE",
|
||||
"=OR(1=1,2=3)": "TRUE",
|
||||
"=OR(\"TRUE\",\"FALSE\")": "TRUE",
|
||||
"=OR(A1:B1)": "TRUE",
|
||||
// SWITCH
|
||||
"=SWITCH(1,1,\"A\",2,\"B\",3,\"C\",\"N\")": "A",
|
||||
"=SWITCH(3,1,\"A\",2,\"B\",3,\"C\",\"N\")": "C",
|
||||
|
@ -1538,8 +1540,9 @@ func TestCalcCellValue(t *testing.T) {
|
|||
"=XOR(1>0,0>1,INT(0),INT(1),A1:A4,2)": "FALSE",
|
||||
// Date and Time Functions
|
||||
// DATE
|
||||
"=DATE(2020,10,21)": "2020-10-21 00:00:00 +0000 UTC",
|
||||
"=DATE(1900,1,1)": "1899-12-31 00:00:00 +0000 UTC",
|
||||
"=DATE(2020,10,21)": "44125",
|
||||
"=DATE(2020,10,21)+1": "44126",
|
||||
"=DATE(1900,1,1)": "1",
|
||||
// DATEDIF
|
||||
"=DATEDIF(43101,43101,\"D\")": "0",
|
||||
"=DATEDIF(43101,43891,\"d\")": "790",
|
||||
|
@ -1748,6 +1751,7 @@ func TestCalcCellValue(t *testing.T) {
|
|||
"=FIND(\"\",\"Original Text\")": "1",
|
||||
"=FIND(\"\",\"Original Text\",2)": "2",
|
||||
"=FIND(\"s\",\"Sales\",2)": "5",
|
||||
"=FIND(D1:E2,\"Month\")": "1",
|
||||
// FINDB
|
||||
"=FINDB(\"T\",\"Original Text\")": "10",
|
||||
"=FINDB(\"t\",\"Original Text\")": "13",
|
||||
|
@ -2113,6 +2117,16 @@ func TestCalcCellValue(t *testing.T) {
|
|||
"=DDB(10000,1000,5,5)": "296",
|
||||
// DISC
|
||||
"=DISC(\"04/01/2016\",\"03/31/2021\",95,100)": "0.01",
|
||||
// DOLLAR
|
||||
"=DOLLAR(1234.56)": "$1,234.56",
|
||||
"=DOLLAR(1234.56,0)": "$1,235",
|
||||
"=DOLLAR(1234.56,1)": "$1,234.6",
|
||||
"=DOLLAR(1234.56,2)": "$1,234.56",
|
||||
"=DOLLAR(1234.56,3)": "$1,234.560",
|
||||
"=DOLLAR(1234.56,-2)": "$1,200",
|
||||
"=DOLLAR(1234.56,-3)": "$1,000",
|
||||
"=DOLLAR(-1234.56,3)": "($1,234.560)",
|
||||
"=DOLLAR(-1234.56,-3)": "($1,000)",
|
||||
// DOLLARDE
|
||||
"=DOLLARDE(1.01,16)": "1.0625",
|
||||
// DOLLARFR
|
||||
|
@ -3663,7 +3677,6 @@ func TestCalcCellValue(t *testing.T) {
|
|||
"=NOT(\"\")": {"#VALUE!", "NOT expects 1 boolean or numeric argument"},
|
||||
// OR
|
||||
"=OR(\"text\")": {"#VALUE!", "#VALUE!"},
|
||||
"=OR(A1:B1)": {"#VALUE!", "#VALUE!"},
|
||||
"=OR(\"1\",\"TRUE\",\"FALSE\")": {"#VALUE!", "#VALUE!"},
|
||||
"=OR()": {"#VALUE!", "OR requires at least 1 argument"},
|
||||
"=OR(1" + strings.Repeat(",1", 30) + ")": {"#VALUE!", "OR accepts at most 30 arguments"},
|
||||
|
@ -4247,6 +4260,12 @@ func TestCalcCellValue(t *testing.T) {
|
|||
"=DISC(\"04/01/2016\",\"03/31/2021\",0,100)": {"#NUM!", "DISC requires pr > 0"},
|
||||
"=DISC(\"04/01/2016\",\"03/31/2021\",95,0)": {"#NUM!", "DISC requires redemption > 0"},
|
||||
"=DISC(\"04/01/2016\",\"03/31/2021\",95,100,5)": {"#NUM!", "invalid basis"},
|
||||
// DOLLAR
|
||||
"DOLLAR()": {"#VALUE!", "DOLLAR requires at least 1 argument"},
|
||||
"DOLLAR(0,0,0)": {"#VALUE!", "DOLLAR requires 1 or 2 arguments"},
|
||||
"DOLLAR(\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
|
||||
"DOLLAR(0,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
|
||||
"DOLLAR(1,200)": {"#VALUE!", "decimal value should be less than 128"},
|
||||
// DOLLARDE
|
||||
"=DOLLARDE()": {"#VALUE!", "DOLLARDE requires 2 arguments"},
|
||||
"=DOLLARDE(\"\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
|
||||
|
@ -4773,7 +4792,7 @@ func TestCalcOR(t *testing.T) {
|
|||
})
|
||||
fn := formulaFuncs{}
|
||||
result := fn.OR(argsList)
|
||||
assert.Equal(t, result.String, "FALSE")
|
||||
assert.Equal(t, result.Value(), "FALSE")
|
||||
assert.Empty(t, result.Error)
|
||||
}
|
||||
|
||||
|
@ -5559,13 +5578,13 @@ func TestCalcSUMIFSAndAVERAGEIFS(t *testing.T) {
|
|||
|
||||
func TestCalcXIRR(t *testing.T) {
|
||||
cellData := [][]interface{}{
|
||||
{-100.00, "01/01/2016"},
|
||||
{20.00, "04/01/2016"},
|
||||
{40.00, "10/01/2016"},
|
||||
{25.00, "02/01/2017"},
|
||||
{8.00, "03/01/2017"},
|
||||
{15.00, "06/01/2017"},
|
||||
{-1e-10, "09/01/2017"},
|
||||
{-100.00, 42370},
|
||||
{20.00, 42461},
|
||||
{40.00, 42644},
|
||||
{25.00, 42767},
|
||||
{8.00, 42795},
|
||||
{15.00, 42887},
|
||||
{-1e-10, 42979},
|
||||
}
|
||||
f := prepareCalcData(cellData)
|
||||
formulaList := map[string]string{
|
||||
|
@ -5581,8 +5600,8 @@ func TestCalcXIRR(t *testing.T) {
|
|||
calcError := map[string][]string{
|
||||
"=XIRR()": {"#VALUE!", "XIRR requires 2 or 3 arguments"},
|
||||
"=XIRR(A1:A4,B1:B4,-1)": {"#VALUE!", "XIRR requires guess > -1"},
|
||||
"=XIRR(\"\",B1:B4)": {"#NUM!", "#NUM!"},
|
||||
"=XIRR(A1:A4,\"\")": {"#NUM!", "#NUM!"},
|
||||
"=XIRR(\"\",B1:B4)": {"#VALUE!", "#VALUE!"},
|
||||
"=XIRR(A1:A4,\"\")": {"#VALUE!", "#VALUE!"},
|
||||
"=XIRR(A1:A4,B1:B4,\"\")": {"#NUM!", "#NUM!"},
|
||||
"=XIRR(A2:A6,B2:B6)": {"#NUM!", "#NUM!"},
|
||||
"=XIRR(A2:A7,B2:B7)": {"#NUM!", "#NUM!"},
|
||||
|
@ -5705,15 +5724,15 @@ func TestCalcXLOOKUP(t *testing.T) {
|
|||
func TestCalcXNPV(t *testing.T) {
|
||||
cellData := [][]interface{}{
|
||||
{nil, 0.05},
|
||||
{"01/01/2016", -10000, nil},
|
||||
{"02/01/2016", 2000},
|
||||
{"05/01/2016", 2400},
|
||||
{"07/01/2016", 2900},
|
||||
{"11/01/2016", 3500},
|
||||
{"01/01/2017", 4100},
|
||||
{42370, -10000, nil},
|
||||
{42401, 2000},
|
||||
{42491, 2400},
|
||||
{42552, 2900},
|
||||
{42675, 3500},
|
||||
{42736, 4100},
|
||||
{},
|
||||
{"02/01/2016"},
|
||||
{"01/01/2016"},
|
||||
{42401},
|
||||
{42370},
|
||||
}
|
||||
f := prepareCalcData(cellData)
|
||||
formulaList := map[string]string{
|
||||
|
@ -5729,9 +5748,9 @@ func TestCalcXNPV(t *testing.T) {
|
|||
"=XNPV()": {"#VALUE!", "XNPV requires 3 arguments"},
|
||||
"=XNPV(\"\",B2:B7,A2:A7)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
|
||||
"=XNPV(0,B2:B7,A2:A7)": {"#VALUE!", "XNPV requires rate > 0"},
|
||||
"=XNPV(B1,\"\",A2:A7)": {"#NUM!", "#NUM!"},
|
||||
"=XNPV(B1,B2:B7,\"\")": {"#NUM!", "#NUM!"},
|
||||
"=XNPV(B1,B2:B7,C2:C7)": {"#NUM!", "#NUM!"},
|
||||
"=XNPV(B1,\"\",A2:A7)": {"#VALUE!", "#VALUE!"},
|
||||
"=XNPV(B1,B2:B7,\"\")": {"#VALUE!", "#VALUE!"},
|
||||
"=XNPV(B1,B2:B7,C2:C7)": {"#VALUE!", "#VALUE!"},
|
||||
"=XNPV(B1,B2,A2)": {"#NUM!", "#NUM!"},
|
||||
"=XNPV(B1,B2:B3,A2:A5)": {"#NUM!", "#NUM!"},
|
||||
"=XNPV(B1,B2:B3,A9:A10)": {"#VALUE!", "#VALUE!"},
|
||||
|
@ -6308,13 +6327,31 @@ func TestFormulaRawCellValueOption(t *testing.T) {
|
|||
raw bool
|
||||
expected string
|
||||
}{
|
||||
{"=\"10e3\"", false, "10000"},
|
||||
{"=VALUE(\"1.0E-07\")", false, "0.00"},
|
||||
{"=VALUE(\"1.0E-07\")", true, "0.0000001"},
|
||||
{"=\"text\"", false, "$text"},
|
||||
{"=\"text\"", true, "text"},
|
||||
{"=\"10e3\"", false, "$10e3"},
|
||||
{"=\"10e3\"", true, "10e3"},
|
||||
{"=\"10\" & \"e3\"", false, "10000"},
|
||||
{"=\"10\" & \"e3\"", false, "$10e3"},
|
||||
{"=\"10\" & \"e3\"", true, "10e3"},
|
||||
{"=\"1111111111111111\"", false, "1.11111111111111E+15"},
|
||||
{"=10e3", false, "10000.00"},
|
||||
{"=10e3", true, "10000"},
|
||||
{"=\"1111111111111111\"", false, "$1111111111111111"},
|
||||
{"=\"1111111111111111\"", true, "1111111111111111"},
|
||||
{"=1111111111111111", false, "1111111111111110.00"},
|
||||
{"=1111111111111111", true, "1.11111111111111E+15"},
|
||||
{"=1444.00000000003", false, "1444.00"},
|
||||
{"=1444.00000000003", true, "1444.00000000003"},
|
||||
{"=1444.000000000003", false, "1444.00"},
|
||||
{"=1444.000000000003", true, "1444"},
|
||||
{"=ROUND(1444.00000000000003,2)", false, "1444.00"},
|
||||
{"=ROUND(1444.00000000000003,2)", true, "1444"},
|
||||
}
|
||||
exp := "0.00;0.00;;$@"
|
||||
styleID, err := f.NewStyle(&Style{CustomNumFmt: &exp})
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "A1", styleID))
|
||||
for _, test := range rawTest {
|
||||
assert.NoError(t, f.SetCellFormula("Sheet1", "A1", test.value))
|
||||
val, err := f.CalcCellValue("Sheet1", "A1", Options{RawCellValue: test.raw})
|
||||
|
|
77
cell.go
77
cell.go
|
@ -15,6 +15,7 @@ import (
|
|||
"bytes"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
@ -143,7 +144,7 @@ func (f *File) SetCellValue(sheet, cell string, value interface{}) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = f.setDefaultTimeStyle(sheet, cell, 21)
|
||||
err = f.setDefaultTimeStyle(sheet, cell, getDurationNumFmt(v))
|
||||
case time.Time:
|
||||
err = f.setCellTimeFunc(sheet, cell, v)
|
||||
case bool:
|
||||
|
@ -255,7 +256,7 @@ func (f *File) setCellTimeFunc(sheet, cell string, value time.Time) error {
|
|||
return err
|
||||
}
|
||||
if isNum {
|
||||
_ = f.setDefaultTimeStyle(sheet, cell, 22)
|
||||
_ = f.setDefaultTimeStyle(sheet, cell, getTimeNumFmt(value))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
@ -385,6 +386,9 @@ func setCellBool(value bool) (t string, v string) {
|
|||
// var x float32 = 1.325
|
||||
// f.SetCellFloat("Sheet1", "A1", float64(x), 2, 32)
|
||||
func (f *File) SetCellFloat(sheet, cell string, value float64, precision, bitSize int) error {
|
||||
if math.IsNaN(value) || math.IsInf(value, 0) {
|
||||
return f.SetCellStr(sheet, cell, fmt.Sprint(value))
|
||||
}
|
||||
f.mu.Lock()
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
if err != nil {
|
||||
|
@ -399,16 +403,19 @@ func (f *File) SetCellFloat(sheet, cell string, value float64, precision, bitSiz
|
|||
return err
|
||||
}
|
||||
c.S = ws.prepareCellStyle(col, row, c.S)
|
||||
c.T, c.V = setCellFloat(value, precision, bitSize)
|
||||
c.IS = nil
|
||||
c.setCellFloat(value, precision, bitSize)
|
||||
return f.removeFormula(c, ws, sheet)
|
||||
}
|
||||
|
||||
// setCellFloat prepares cell type and string type cell value by a given float
|
||||
// value.
|
||||
func setCellFloat(value float64, precision, bitSize int) (t string, v string) {
|
||||
v = strconv.FormatFloat(value, 'f', precision, bitSize)
|
||||
return
|
||||
func (c *xlsxC) setCellFloat(value float64, precision, bitSize int) {
|
||||
if math.IsNaN(value) || math.IsInf(value, 0) {
|
||||
c.setInlineStr(fmt.Sprint(value))
|
||||
return
|
||||
}
|
||||
c.T, c.V = "", strconv.FormatFloat(value, 'f', precision, bitSize)
|
||||
c.IS = nil
|
||||
}
|
||||
|
||||
// SetCellStr provides a function to set string type value of a cell. Total
|
||||
|
@ -505,7 +512,9 @@ func trimCellValue(value string, escape bool) (v string, ns xml.Attr) {
|
|||
}
|
||||
if escape {
|
||||
var buf bytes.Buffer
|
||||
_ = xml.EscapeText(&buf, []byte(value))
|
||||
enc := xml.NewEncoder(&buf)
|
||||
_ = enc.EncodeToken(xml.CharData(value))
|
||||
enc.Flush()
|
||||
value = buf.String()
|
||||
}
|
||||
if len(value) > 0 {
|
||||
|
@ -955,14 +964,36 @@ type HyperlinkOpts struct {
|
|||
Tooltip *string
|
||||
}
|
||||
|
||||
// removeHyperLink remove hyperlink for worksheet and delete relationships for
|
||||
// the worksheet by given sheet name and cell reference. Note that if the cell
|
||||
// in a range reference, the whole hyperlinks will be deleted.
|
||||
func (f *File) removeHyperLink(ws *xlsxWorksheet, sheet, cell string) error {
|
||||
for idx := 0; idx < len(ws.Hyperlinks.Hyperlink); idx++ {
|
||||
link := ws.Hyperlinks.Hyperlink[idx]
|
||||
ok, err := f.checkCellInRangeRef(cell, link.Ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if link.Ref == cell || ok {
|
||||
ws.Hyperlinks.Hyperlink = append(ws.Hyperlinks.Hyperlink[:idx], ws.Hyperlinks.Hyperlink[idx+1:]...)
|
||||
idx--
|
||||
f.deleteSheetRelationships(sheet, link.RID)
|
||||
}
|
||||
}
|
||||
if len(ws.Hyperlinks.Hyperlink) == 0 {
|
||||
ws.Hyperlinks = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetCellHyperLink provides a function to set cell hyperlink by given
|
||||
// worksheet name and link URL address. LinkType defines two types of
|
||||
// worksheet name and link URL address. LinkType defines three types of
|
||||
// hyperlink "External" for website or "Location" for moving to one of cell in
|
||||
// this workbook. Maximum limit hyperlinks in a worksheet is 65530. This
|
||||
// function is only used to set the hyperlink of the cell and doesn't affect
|
||||
// the value of the cell. If you need to set the value of the cell, please use
|
||||
// the other functions such as `SetCellStyle` or `SetSheetRow`. The below is
|
||||
// example for external link.
|
||||
// this workbook or "None" for remove hyperlink. Maximum limit hyperlinks in a
|
||||
// worksheet is 65530. This function is only used to set the hyperlink of the
|
||||
// cell and doesn't affect the value of the cell. If you need to set the value
|
||||
// of the cell, please use the other functions such as `SetCellStyle` or
|
||||
// `SetSheetRow`. The below is example for external link.
|
||||
//
|
||||
// display, tooltip := "https://github.com/xuri/excelize", "Excelize on GitHub"
|
||||
// if err := f.SetCellHyperLink("Sheet1", "A3",
|
||||
|
@ -1030,6 +1061,8 @@ func (f *File) SetCellHyperLink(sheet, cell, link, linkType string, opts ...Hype
|
|||
Ref: cell,
|
||||
Location: link,
|
||||
}
|
||||
case "None":
|
||||
return f.removeHyperLink(ws, sheet, cell)
|
||||
default:
|
||||
return newInvalidLinkTypeError(linkType)
|
||||
}
|
||||
|
@ -1082,11 +1115,11 @@ func (f *File) GetCellRichText(sheet, cell string) (runs []RichTextRun, err erro
|
|||
runs = getCellRichText(c.IS)
|
||||
return
|
||||
}
|
||||
if c.T == "" {
|
||||
if c.T != "s" || c.V == "" {
|
||||
return
|
||||
}
|
||||
siIdx, err := strconv.Atoi(c.V)
|
||||
if err != nil || c.T != "s" {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
sst, err := f.sharedStringsReader()
|
||||
|
@ -1426,8 +1459,8 @@ func (f *File) getCellStringFunc(sheet, cell string, fn func(x *xlsxWorksheet, c
|
|||
return "", err
|
||||
}
|
||||
lastRowNum := 0
|
||||
if l := len(ws.SheetData.Row); l > 0 && ws.SheetData.Row[l-1].R != nil {
|
||||
lastRowNum = *ws.SheetData.Row[l-1].R
|
||||
if l := len(ws.SheetData.Row); l > 0 {
|
||||
lastRowNum = ws.SheetData.Row[l-1].R
|
||||
}
|
||||
|
||||
// keep in mind: row starts from 1
|
||||
|
@ -1437,7 +1470,7 @@ func (f *File) getCellStringFunc(sheet, cell string, fn func(x *xlsxWorksheet, c
|
|||
|
||||
for rowIdx := range ws.SheetData.Row {
|
||||
rowData := &ws.SheetData.Row[rowIdx]
|
||||
if rowData.R != nil && *rowData.R != row {
|
||||
if rowData.R != row {
|
||||
continue
|
||||
}
|
||||
for colIdx := range rowData.C {
|
||||
|
@ -1655,8 +1688,10 @@ func parseSharedFormula(dCol, dRow int, orig []byte) (res string, start int) {
|
|||
// Note that this function not validate ref tag to check the cell whether in
|
||||
// allow range reference, and always return origin shared formula.
|
||||
func getSharedFormula(ws *xlsxWorksheet, si int, cell string) string {
|
||||
for _, r := range ws.SheetData.Row {
|
||||
for _, c := range r.C {
|
||||
for row := 0; row < len(ws.SheetData.Row); row++ {
|
||||
r := &ws.SheetData.Row[row]
|
||||
for column := 0; column < len(r.C); column++ {
|
||||
c := &r.C[column]
|
||||
if c.F != nil && c.F.Ref != "" && c.F.T == STCellFormulaTypeShared && c.F.Si != nil && *c.F.Si == si {
|
||||
col, row, _ := CellNameToCoordinates(cell)
|
||||
sharedCol, sharedRow, _ := CellNameToCoordinates(c.R)
|
||||
|
|
68
cell_test.go
68
cell_test.go
|
@ -42,6 +42,9 @@ func TestConcurrency(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
// Concurrency set cell style
|
||||
assert.NoError(t, f.SetCellStyle("Sheet1", "A3", "A3", style))
|
||||
// Concurrency get cell style
|
||||
_, err = f.GetCellStyle("Sheet1", "A3")
|
||||
assert.NoError(t, err)
|
||||
// Concurrency add picture
|
||||
assert.NoError(t, f.AddPicture("Sheet1", "F21", filepath.Join("test", "images", "excel.jpg"),
|
||||
&GraphicOptions{
|
||||
|
@ -88,6 +91,14 @@ func TestConcurrency(t *testing.T) {
|
|||
visible, err := f.GetColVisible("Sheet1", "A")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, true, visible)
|
||||
// Concurrency add data validation
|
||||
dv := NewDataValidation(true)
|
||||
dv.Sqref = fmt.Sprintf("A%d:B%d", val, val)
|
||||
assert.NoError(t, dv.SetRange(10, 20, DataValidationTypeWhole, DataValidationOperatorGreaterThan))
|
||||
dv.SetInput(fmt.Sprintf("title:%d", val), strconv.Itoa(val))
|
||||
assert.NoError(t, f.AddDataValidation("Sheet1", dv))
|
||||
// Concurrency delete data validation with reference sequence
|
||||
assert.NoError(t, f.DeleteDataValidation("Sheet1", dv.Sqref))
|
||||
wg.Done()
|
||||
}(i, t)
|
||||
}
|
||||
|
@ -97,6 +108,10 @@ func TestConcurrency(t *testing.T) {
|
|||
t.Error(err)
|
||||
}
|
||||
assert.Equal(t, "1", val)
|
||||
// Test the length of data validation
|
||||
dataValidations, err := f.GetDataValidations("Sheet1")
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, dataValidations, 0)
|
||||
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestConcurrency.xlsx")))
|
||||
assert.NoError(t, f.Close())
|
||||
}
|
||||
|
@ -266,7 +281,7 @@ func TestSetCellValue(t *testing.T) {
|
|||
f.Pkg.Store(defaultXMLPathSharedStrings, []byte(fmt.Sprintf(`<sst xmlns="%s" count="2" uniqueCount="1"><si><t>a</t></si><si><t>a</t></si></sst>`, NameSpaceSpreadSheet.Value)))
|
||||
f.Sheet.Store("xl/worksheets/sheet1.xml", &xlsxWorksheet{
|
||||
SheetData: xlsxSheetData{Row: []xlsxRow{
|
||||
{R: intPtr(1), C: []xlsxC{{R: "A1", T: "str", V: "1"}}},
|
||||
{R: 1, C: []xlsxC{{R: "A1", T: "str", V: "1"}}},
|
||||
}},
|
||||
})
|
||||
assert.NoError(t, f.SetCellValue("Sheet1", "A1", "b"))
|
||||
|
@ -277,6 +292,42 @@ func TestSetCellValue(t *testing.T) {
|
|||
val, err = f.GetCellValue("Sheet1", "B1")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "b", val)
|
||||
|
||||
f = NewFile()
|
||||
// Test set cell value with an IEEE 754 "not-a-number" value or infinity
|
||||
for num, expected := range map[float64]string{
|
||||
math.NaN(): "NaN",
|
||||
math.Inf(0): "+Inf",
|
||||
math.Inf(-1): "-Inf",
|
||||
} {
|
||||
assert.NoError(t, f.SetCellValue("Sheet1", "A1", num))
|
||||
val, err := f.GetCellValue("Sheet1", "A1")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expected, val)
|
||||
}
|
||||
// Test set cell value with time duration
|
||||
for val, expected := range map[time.Duration]string{
|
||||
time.Hour*21 + time.Minute*51 + time.Second*44: "21:51:44",
|
||||
time.Hour*21 + time.Minute*50: "21:50",
|
||||
time.Hour*24 + time.Minute*51 + time.Second*44: "24:51:44",
|
||||
time.Hour*24 + time.Minute*50: "24:50:00",
|
||||
} {
|
||||
assert.NoError(t, f.SetCellValue("Sheet1", "A1", val))
|
||||
val, err := f.GetCellValue("Sheet1", "A1")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expected, val)
|
||||
}
|
||||
// Test set cell value with time
|
||||
for val, expected := range map[time.Time]string{
|
||||
time.Date(2024, time.October, 1, 0, 0, 0, 0, time.UTC): "Oct-24",
|
||||
time.Date(2024, time.October, 10, 0, 0, 0, 0, time.UTC): "10-10-24",
|
||||
time.Date(2024, time.October, 10, 12, 0, 0, 0, time.UTC): "10/10/24 12:00",
|
||||
} {
|
||||
assert.NoError(t, f.SetCellValue("Sheet1", "A1", val))
|
||||
val, err := f.GetCellValue("Sheet1", "A1")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expected, val)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetCellValues(t *testing.T) {
|
||||
|
@ -286,7 +337,7 @@ func TestSetCellValues(t *testing.T) {
|
|||
|
||||
v, err := f.GetCellValue("Sheet1", "A1")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, v, "12/31/10 00:00")
|
||||
assert.Equal(t, v, "12-31-10")
|
||||
|
||||
// Test date value lower than min date supported by Excel
|
||||
err = f.SetCellValue("Sheet1", "A1", time.Date(1600, time.December, 31, 0, 0, 0, 0, time.UTC))
|
||||
|
@ -375,6 +426,9 @@ func TestGetCellValue(t *testing.T) {
|
|||
f.Sheet.Delete("xl/worksheets/sheet1.xml")
|
||||
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `<row r="0"><c r="H6" t="inlineStr"><is><t>H6</t></is></c><c r="A1" t="inlineStr"><is><t>r0A6</t></is></c><c r="F4" t="inlineStr"><is><t>F4</t></is></c></row><row><c r="A1" t="inlineStr"><is><t>A6</t></is></c><c r="B1" t="inlineStr"><is><t>B6</t></is></c><c r="C1" t="inlineStr"><is><t>C6</t></is></c></row><row r="3"><c r="A3"><v>100</v></c><c r="B3" t="inlineStr"><is><t>B3</t></is></c></row>`)))
|
||||
f.checked = sync.Map{}
|
||||
cell, err = f.GetCellValue("Sheet1", "H6")
|
||||
assert.Equal(t, "H6", cell)
|
||||
assert.NoError(t, err)
|
||||
rows, err = f.GetRows("Sheet1")
|
||||
assert.Equal(t, [][]string{
|
||||
{"A6", "B6", "C6"},
|
||||
|
@ -385,9 +439,6 @@ func TestGetCellValue(t *testing.T) {
|
|||
{"", "", "", "", "", "", "", "H6"},
|
||||
}, rows)
|
||||
assert.NoError(t, err)
|
||||
cell, err = f.GetCellValue("Sheet1", "H6")
|
||||
assert.Equal(t, "H6", cell)
|
||||
assert.NoError(t, err)
|
||||
|
||||
f.Sheet.Delete("xl/worksheets/sheet1.xml")
|
||||
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `<row><c r="A1" t="inlineStr"><is><t>A1</t></is></c></row><row></row><row><c r="A3" t="inlineStr"><is><t>A3</t></is></c></row>`)))
|
||||
|
@ -757,6 +808,13 @@ func TestGetCellRichText(t *testing.T) {
|
|||
runs, err = f.GetCellRichText("Sheet1", "A1")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 0, len(runs))
|
||||
// Test get cell rich text when string item index is invalid
|
||||
ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml")
|
||||
assert.True(t, ok)
|
||||
ws.(*xlsxWorksheet).SheetData.Row[0].C[0] = xlsxC{T: "s", V: "A", IS: &xlsxSI{}}
|
||||
runs, err = f.GetCellRichText("Sheet1", "A1")
|
||||
assert.EqualError(t, err, "strconv.Atoi: parsing \"A\": invalid syntax")
|
||||
assert.Equal(t, 0, len(runs))
|
||||
// Test get cell rich text on invalid string item index
|
||||
ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml")
|
||||
assert.True(t, ok)
|
||||
|
|
44
chart.go
44
chart.go
|
@ -85,11 +85,25 @@ type ChartLineType byte
|
|||
|
||||
// This section defines the currently supported chart line types enumeration.
|
||||
const (
|
||||
ChartLineSolid ChartLineType = iota
|
||||
ChartLineUnset ChartLineType = iota
|
||||
ChartLineSolid
|
||||
ChartLineNone
|
||||
ChartLineAutomatic
|
||||
)
|
||||
|
||||
// ChartTickLabelPositionType is the type of supported chart tick label position
|
||||
// types.
|
||||
type ChartTickLabelPositionType byte
|
||||
|
||||
// This section defines the supported chart tick label position types
|
||||
// enumeration.
|
||||
const (
|
||||
ChartTickLabelNextToAxis ChartTickLabelPositionType = iota
|
||||
ChartTickLabelHigh
|
||||
ChartTickLabelLow
|
||||
ChartTickLabelNone
|
||||
)
|
||||
|
||||
// This section defines the default value of chart properties.
|
||||
var (
|
||||
chartView3DRotX = map[ChartType]int{
|
||||
|
@ -484,7 +498,13 @@ var (
|
|||
true: "r",
|
||||
false: "l",
|
||||
}
|
||||
valTickLblPos = map[ChartType]string{
|
||||
tickLblPosVal = map[ChartTickLabelPositionType]string{
|
||||
ChartTickLabelNextToAxis: "nextTo",
|
||||
ChartTickLabelHigh: "high",
|
||||
ChartTickLabelLow: "low",
|
||||
ChartTickLabelNone: "none",
|
||||
}
|
||||
tickLblPosNone = map[ChartType]string{
|
||||
Contour: "none",
|
||||
WireframeContour: "none",
|
||||
}
|
||||
|
@ -830,6 +850,7 @@ func (opts *Chart) parseTitle() {
|
|||
// ReverseOrder
|
||||
// Maximum
|
||||
// Minimum
|
||||
// Alignment
|
||||
// Font
|
||||
// NumFmt
|
||||
// Title
|
||||
|
@ -844,6 +865,7 @@ func (opts *Chart) parseTitle() {
|
|||
// ReverseOrder
|
||||
// Maximum
|
||||
// Minimum
|
||||
// Alignment
|
||||
// Font
|
||||
// LogBase
|
||||
// NumFmt
|
||||
|
@ -876,6 +898,24 @@ func (opts *Chart) parseTitle() {
|
|||
// Minimum: Specifies that the fixed minimum, 0 is auto. The 'Minimum' property
|
||||
// is optional. The default value is auto.
|
||||
//
|
||||
// Alignment: Specifies that the alignment of the horizontal and vertical axis.
|
||||
// The properties of font that can be set are:
|
||||
//
|
||||
// TextRotation
|
||||
// Vertical
|
||||
//
|
||||
// The value of 'TextRotation' that can be set from -90 to 90:
|
||||
//
|
||||
// The value of 'Vertical' that can be set are:
|
||||
//
|
||||
// horz
|
||||
// vert
|
||||
// vert270
|
||||
// wordArtVert
|
||||
// eaVert
|
||||
// mongolianVert
|
||||
// wordArtVertRtl
|
||||
//
|
||||
// Font: Specifies that the font of the horizontal and vertical axis. The
|
||||
// properties of font that can be set are:
|
||||
//
|
||||
|
|
|
@ -216,9 +216,9 @@ func TestAddChart(t *testing.T) {
|
|||
}{
|
||||
{sheetName: "Sheet1", cell: "P1", opts: &Chart{Type: Col, Series: series, Format: format, Legend: ChartLegend{Position: "none", ShowLegendKey: true}, Title: []RichTextRun{{Text: "2D Column Chart"}}, PlotArea: plotArea, Border: ChartLine{Type: ChartLineNone}, ShowBlanksAs: "zero", XAxis: ChartAxis{Font: Font{Bold: true, Italic: true, Underline: "dbl", Family: "Times New Roman", Size: 15, Strike: true, Color: "000000"}, Title: []RichTextRun{{Text: "Primary Horizontal Axis Title"}}}, YAxis: ChartAxis{Font: Font{Bold: false, Italic: false, Underline: "sng", Color: "777777"}, Title: []RichTextRun{{Text: "Primary Vertical Axis Title", Font: &Font{Color: "777777", Bold: true, Italic: true, Size: 12}}}}}},
|
||||
{sheetName: "Sheet1", cell: "X1", opts: &Chart{Type: ColStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "2D Stacked Column Chart"}}, PlotArea: plotArea, Fill: Fill{Type: "pattern", Pattern: 1}, Border: ChartLine{Type: ChartLineAutomatic}, ShowBlanksAs: "zero"}},
|
||||
{sheetName: "Sheet1", cell: "P16", opts: &Chart{Type: ColPercentStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "100% Stacked Column Chart"}}, PlotArea: plotArea, Fill: Fill{Type: "pattern", Color: []string{"EEEEEE"}, Pattern: 1}, Border: ChartLine{Type: ChartLineSolid, Width: 2}, ShowBlanksAs: "zero"}},
|
||||
{sheetName: "Sheet1", cell: "P16", opts: &Chart{Type: ColPercentStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "100% Stacked Column Chart"}}, PlotArea: plotArea, Fill: Fill{Type: "pattern", Color: []string{"EEEEEE"}, Pattern: 1}, Border: ChartLine{Type: ChartLineSolid, Width: 2}, ShowBlanksAs: "zero", XAxis: ChartAxis{Alignment: Alignment{Vertical: "wordArtVertRtl", TextRotation: 0}}}},
|
||||
{sheetName: "Sheet1", cell: "X16", opts: &Chart{Type: Col3DClustered, Series: series, Format: format, Legend: ChartLegend{Position: "bottom", ShowLegendKey: false}, Title: []RichTextRun{{Text: "3D Clustered Column Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
|
||||
{sheetName: "Sheet1", cell: "P30", opts: &Chart{Type: Col3DStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Stacked Column Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
|
||||
{sheetName: "Sheet1", cell: "P30", opts: &Chart{Type: Col3DStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Stacked Column Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{Alignment: Alignment{Vertical: "vert", TextRotation: 0}}}},
|
||||
{sheetName: "Sheet1", cell: "X30", opts: &Chart{Type: Col3DPercentStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D 100% Stacked Column Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
|
||||
{sheetName: "Sheet1", cell: "X45", opts: &Chart{Type: Radar, Series: series, Format: format, Legend: ChartLegend{Position: "top_right", ShowLegendKey: false}, Title: []RichTextRun{{Text: "Radar Chart"}}, PlotArea: plotArea, ShowBlanksAs: "span"}},
|
||||
{sheetName: "Sheet1", cell: "AF1", opts: &Chart{Type: Col3DConeStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Column Cone Stacked Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
|
||||
|
@ -233,11 +233,11 @@ func TestAddChart(t *testing.T) {
|
|||
{sheetName: "Sheet1", cell: "AV16", opts: &Chart{Type: Col3DCylinderClustered, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Column Cylinder Clustered Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
|
||||
{sheetName: "Sheet1", cell: "AV30", opts: &Chart{Type: Col3DCylinderPercentStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Column Cylinder Percent Stacked Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
|
||||
{sheetName: "Sheet1", cell: "AV45", opts: &Chart{Type: Col3DCylinder, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Column Cylinder Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
|
||||
{sheetName: "Sheet1", cell: "P45", opts: &Chart{Type: Col3D, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Column Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
|
||||
{sheetName: "Sheet1", cell: "P45", opts: &Chart{Type: Col3D, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Column Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{Alignment: Alignment{Vertical: "vert270", TextRotation: 0}}}},
|
||||
{sheetName: "Sheet2", cell: "P1", opts: &Chart{Type: Line3D, Series: series2, Format: format, Legend: ChartLegend{Position: "top", ShowLegendKey: false}, Title: []RichTextRun{{Text: "3D Line Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{MajorGridLines: true, MinorGridLines: true, TickLabelSkip: 1, NumFmt: ChartNumFmt{CustomNumFmt: "General"}}, YAxis: ChartAxis{MajorGridLines: true, MinorGridLines: true, MajorUnit: 1, NumFmt: ChartNumFmt{CustomNumFmt: "General"}}}},
|
||||
{sheetName: "Sheet2", cell: "X1", opts: &Chart{Type: Scatter, Series: series, Format: format, Legend: ChartLegend{Position: "bottom", ShowLegendKey: false}, Title: []RichTextRun{{Text: "Scatter Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
|
||||
{sheetName: "Sheet2", cell: "P16", opts: &Chart{Type: Doughnut, Series: series3, Format: format, Legend: ChartLegend{Position: "right", ShowLegendKey: false}, Title: []RichTextRun{{Text: "Doughnut Chart"}}, PlotArea: ChartPlotArea{ShowBubbleSize: false, ShowCatName: false, ShowLeaderLines: false, ShowPercent: true, ShowSerName: false, ShowVal: false}, ShowBlanksAs: "zero", HoleSize: 30}},
|
||||
{sheetName: "Sheet2", cell: "X16", opts: &Chart{Type: Line, Series: series2, Format: format, Legend: ChartLegend{Position: "top", ShowLegendKey: false}, Title: []RichTextRun{{Text: "Line Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{MajorGridLines: true, MinorGridLines: true, TickLabelSkip: 1}, YAxis: ChartAxis{MajorGridLines: true, MinorGridLines: true, MajorUnit: 1}}},
|
||||
{sheetName: "Sheet2", cell: "X16", opts: &Chart{Type: Line, Series: series2, Format: format, Legend: ChartLegend{Position: "top", ShowLegendKey: false}, Title: []RichTextRun{{Text: "Line Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{MajorGridLines: true, MinorGridLines: true, TickLabelSkip: 1, TickLabelPosition: ChartTickLabelLow}, YAxis: ChartAxis{MajorGridLines: true, MinorGridLines: true, MajorUnit: 1}}},
|
||||
{sheetName: "Sheet2", cell: "P32", opts: &Chart{Type: Pie3D, Series: series3, Format: format, Legend: ChartLegend{Position: "bottom", ShowLegendKey: false}, Title: []RichTextRun{{Text: "3D Column Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}},
|
||||
{sheetName: "Sheet2", cell: "X32", opts: &Chart{Type: Pie, Series: series3, Format: format, Legend: ChartLegend{Position: "bottom", ShowLegendKey: false}, Title: []RichTextRun{{Text: "Pie Chart"}}, PlotArea: ChartPlotArea{ShowBubbleSize: true, ShowCatName: false, ShowLeaderLines: false, ShowPercent: true, ShowSerName: false, ShowVal: false, NumFmt: ChartNumFmt{CustomNumFmt: "0.00%;0;;"}}, ShowBlanksAs: "gap"}},
|
||||
// bar series chart
|
||||
|
@ -409,7 +409,7 @@ func TestDeleteChart(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestChartWithLogarithmicBase(t *testing.T) {
|
||||
// Create test XLSX file with data
|
||||
// Create test workbook with data
|
||||
f := NewFile()
|
||||
sheet1 := f.GetSheetName(0)
|
||||
categories := map[string]float64{
|
||||
|
@ -454,14 +454,14 @@ func TestChartWithLogarithmicBase(t *testing.T) {
|
|||
assert.NoError(t, f.AddChart(sheet1, c.cell, c.opts))
|
||||
}
|
||||
|
||||
// Export XLSX file for human confirmation
|
||||
// Export workbook for human confirmation
|
||||
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestChartWithLogarithmicBase10.xlsx")))
|
||||
|
||||
// Write the XLSX file to a buffer
|
||||
// Write the workbook to a buffer
|
||||
var buffer bytes.Buffer
|
||||
assert.NoError(t, f.Write(&buffer))
|
||||
|
||||
// Read back the XLSX file from the buffer
|
||||
// Read back the workbook from the buffer
|
||||
newFile, err := OpenReader(&buffer)
|
||||
assert.NoError(t, err)
|
||||
|
||||
|
|
14
col.go
14
col.go
|
@ -18,7 +18,7 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/mohae/deepcopy"
|
||||
"github.com/tiendc/go-deepcopy"
|
||||
)
|
||||
|
||||
// Define the default cell size and EMU unit of measurement.
|
||||
|
@ -63,16 +63,16 @@ type Cols struct {
|
|||
// fmt.Println()
|
||||
// }
|
||||
func (f *File) GetCols(sheet string, opts ...Options) ([][]string, error) {
|
||||
if _, err := f.workSheetReader(sheet); err != nil {
|
||||
cols, err := f.Cols(sheet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cols, err := f.Cols(sheet)
|
||||
results := make([][]string, 0, 64)
|
||||
for cols.Next() {
|
||||
col, _ := cols.Rows(opts...)
|
||||
results = append(results, col)
|
||||
}
|
||||
return results, err
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// Next will return true if the next column is found.
|
||||
|
@ -533,7 +533,8 @@ func (f *File) SetColWidth(sheet, startCol, endCol string, width float64) error
|
|||
func flatCols(col xlsxCol, cols []xlsxCol, replacer func(fc, c xlsxCol) xlsxCol) []xlsxCol {
|
||||
var fc []xlsxCol
|
||||
for i := col.Min; i <= col.Max; i++ {
|
||||
c := deepcopy.Copy(col).(xlsxCol)
|
||||
var c xlsxCol
|
||||
deepcopy.Copy(&c, col)
|
||||
c.Min, c.Max = i, i
|
||||
fc = append(fc, c)
|
||||
}
|
||||
|
@ -551,7 +552,8 @@ func flatCols(col xlsxCol, cols []xlsxCol, replacer func(fc, c xlsxCol) xlsxCol)
|
|||
fc[idx] = replacer(fc[idx], column)
|
||||
continue
|
||||
}
|
||||
c := deepcopy.Copy(column).(xlsxCol)
|
||||
var c xlsxCol
|
||||
deepcopy.Copy(&c, column)
|
||||
c.Min, c.Max = i, i
|
||||
fc = append(fc, c)
|
||||
}
|
||||
|
|
|
@ -140,7 +140,7 @@ func TestGetColsError(t *testing.T) {
|
|||
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(`<worksheet xmlns="%s"><sheetData><row r="A"><c r="2" t="inlineStr"><is><t>B</t></is></c></row></sheetData></worksheet>`, NameSpaceSpreadSheet.Value)))
|
||||
f.checked = sync.Map{}
|
||||
_, err = f.GetCols("Sheet1")
|
||||
assert.EqualError(t, err, `strconv.ParseInt: parsing "A": invalid syntax`)
|
||||
assert.EqualError(t, err, `strconv.Atoi: parsing "A": invalid syntax`)
|
||||
|
||||
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(`<worksheet xmlns="%s"><sheetData><row r="2"><c r="A" t="inlineStr"><is><t>B</t></is></c></row></sheetData></worksheet>`, NameSpaceSpreadSheet.Value)))
|
||||
_, err = f.GetCols("Sheet1")
|
||||
|
|
|
@ -13,6 +13,7 @@ package excelize
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"strings"
|
||||
"unicode/utf16"
|
||||
|
@ -222,8 +223,9 @@ func (dv *DataValidation) SetSqref(sqref string) {
|
|||
}
|
||||
|
||||
// AddDataValidation provides set data validation on a range of the worksheet
|
||||
// by given data validation object and worksheet name. The data validation
|
||||
// object can be created by NewDataValidation function.
|
||||
// by given data validation object and worksheet name. This function is
|
||||
// concurrency safe. The data validation object can be created by
|
||||
// NewDataValidation function.
|
||||
//
|
||||
// Example 1, set data validation on Sheet1!A1:B2 with validation criteria
|
||||
// settings, show error alert after invalid data is entered with "Stop" style
|
||||
|
@ -256,6 +258,8 @@ func (f *File) AddDataValidation(sheet string, dv *DataValidation) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ws.mu.Lock()
|
||||
defer ws.mu.Unlock()
|
||||
if nil == ws.DataValidations {
|
||||
ws.DataValidations = new(xlsxDataValidations)
|
||||
}
|
||||
|
@ -290,46 +294,83 @@ func (f *File) GetDataValidations(sheet string) ([]*DataValidation, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ws.DataValidations == nil || len(ws.DataValidations.DataValidation) == 0 {
|
||||
return nil, err
|
||||
var (
|
||||
dataValidations []*DataValidation
|
||||
decodeExtLst = new(decodeExtLst)
|
||||
decodeDataValidations *xlsxDataValidations
|
||||
ext *xlsxExt
|
||||
)
|
||||
if ws.DataValidations != nil {
|
||||
dataValidations = append(dataValidations, getDataValidations(ws.DataValidations)...)
|
||||
}
|
||||
var dvs []*DataValidation
|
||||
for _, dv := range ws.DataValidations.DataValidation {
|
||||
if dv != nil {
|
||||
dataValidation := &DataValidation{
|
||||
AllowBlank: dv.AllowBlank,
|
||||
Error: dv.Error,
|
||||
ErrorStyle: dv.ErrorStyle,
|
||||
ErrorTitle: dv.ErrorTitle,
|
||||
Operator: dv.Operator,
|
||||
Prompt: dv.Prompt,
|
||||
PromptTitle: dv.PromptTitle,
|
||||
ShowDropDown: dv.ShowDropDown,
|
||||
ShowErrorMessage: dv.ShowErrorMessage,
|
||||
ShowInputMessage: dv.ShowInputMessage,
|
||||
Sqref: dv.Sqref,
|
||||
Type: dv.Type,
|
||||
if ws.ExtLst != nil {
|
||||
if err = f.xmlNewDecoder(strings.NewReader("<extLst>" + ws.ExtLst.Ext + "</extLst>")).
|
||||
Decode(decodeExtLst); err != nil && err != io.EOF {
|
||||
return dataValidations, err
|
||||
}
|
||||
for _, ext = range decodeExtLst.Ext {
|
||||
if ext.URI == ExtURIDataValidations {
|
||||
decodeDataValidations = new(xlsxDataValidations)
|
||||
_ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(decodeDataValidations)
|
||||
dataValidations = append(dataValidations, getDataValidations(decodeDataValidations)...)
|
||||
}
|
||||
if dv.Formula1 != nil {
|
||||
dataValidation.Formula1 = unescapeDataValidationFormula(dv.Formula1.Content)
|
||||
}
|
||||
if dv.Formula2 != nil {
|
||||
dataValidation.Formula2 = unescapeDataValidationFormula(dv.Formula2.Content)
|
||||
}
|
||||
dvs = append(dvs, dataValidation)
|
||||
}
|
||||
}
|
||||
return dvs, err
|
||||
return dataValidations, err
|
||||
}
|
||||
|
||||
// getDataValidations returns data validations list by given worksheet data
|
||||
// validations.
|
||||
func getDataValidations(dvs *xlsxDataValidations) []*DataValidation {
|
||||
if dvs == nil {
|
||||
return nil
|
||||
}
|
||||
var dataValidations []*DataValidation
|
||||
for _, dv := range dvs.DataValidation {
|
||||
if dv == nil {
|
||||
continue
|
||||
}
|
||||
dataValidation := &DataValidation{
|
||||
AllowBlank: dv.AllowBlank,
|
||||
Error: dv.Error,
|
||||
ErrorStyle: dv.ErrorStyle,
|
||||
ErrorTitle: dv.ErrorTitle,
|
||||
Operator: dv.Operator,
|
||||
Prompt: dv.Prompt,
|
||||
PromptTitle: dv.PromptTitle,
|
||||
ShowDropDown: dv.ShowDropDown,
|
||||
ShowErrorMessage: dv.ShowErrorMessage,
|
||||
ShowInputMessage: dv.ShowInputMessage,
|
||||
Sqref: dv.Sqref,
|
||||
Type: dv.Type,
|
||||
}
|
||||
if dv.Formula1 != nil {
|
||||
dataValidation.Formula1 = unescapeDataValidationFormula(dv.Formula1.Content)
|
||||
}
|
||||
if dv.Formula2 != nil {
|
||||
dataValidation.Formula2 = unescapeDataValidationFormula(dv.Formula2.Content)
|
||||
}
|
||||
if dv.XMSqref != "" {
|
||||
dataValidation.Sqref = dv.XMSqref
|
||||
dataValidation.Formula1 = strings.TrimSuffix(strings.TrimPrefix(dataValidation.Formula1, "<xm:f>"), "</xm:f>")
|
||||
dataValidation.Formula2 = strings.TrimSuffix(strings.TrimPrefix(dataValidation.Formula2, "<xm:f>"), "</xm:f>")
|
||||
}
|
||||
dataValidations = append(dataValidations, dataValidation)
|
||||
}
|
||||
return dataValidations
|
||||
}
|
||||
|
||||
// DeleteDataValidation delete data validation by given worksheet name and
|
||||
// reference sequence. All data validations in the worksheet will be deleted
|
||||
// reference sequence. This function is concurrency safe.
|
||||
// All data validations in the worksheet will be deleted
|
||||
// if not specify reference sequence parameter.
|
||||
func (f *File) DeleteDataValidation(sheet string, sqref ...string) error {
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ws.mu.Lock()
|
||||
defer ws.mu.Unlock()
|
||||
if ws.DataValidations == nil {
|
||||
return nil
|
||||
}
|
||||
|
@ -337,14 +378,14 @@ func (f *File) DeleteDataValidation(sheet string, sqref ...string) error {
|
|||
ws.DataValidations = nil
|
||||
return nil
|
||||
}
|
||||
delCells, err := f.flatSqref(sqref[0])
|
||||
delCells, err := flatSqref(sqref[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dv := ws.DataValidations
|
||||
for i := 0; i < len(dv.DataValidation); i++ {
|
||||
var applySqref []string
|
||||
colCells, err := f.flatSqref(dv.DataValidation[i].Sqref)
|
||||
colCells, err := flatSqref(dv.DataValidation[i].Sqref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -357,7 +398,7 @@ func (f *File) DeleteDataValidation(sheet string, sqref ...string) error {
|
|||
}
|
||||
}
|
||||
for _, col := range colCells {
|
||||
applySqref = append(applySqref, f.squashSqref(col)...)
|
||||
applySqref = append(applySqref, squashSqref(col)...)
|
||||
}
|
||||
dv.DataValidation[i].Sqref = strings.Join(applySqref, " ")
|
||||
if len(applySqref) == 0 {
|
||||
|
@ -373,7 +414,7 @@ func (f *File) DeleteDataValidation(sheet string, sqref ...string) error {
|
|||
}
|
||||
|
||||
// squashSqref generates cell reference sequence by given cells coordinates list.
|
||||
func (f *File) squashSqref(cells [][]int) []string {
|
||||
func squashSqref(cells [][]int) []string {
|
||||
if len(cells) == 1 {
|
||||
cell, _ := CoordinatesToCellName(cells[0][0], cells[0][1])
|
||||
return []string{cell}
|
||||
|
@ -384,7 +425,7 @@ func (f *File) squashSqref(cells [][]int) []string {
|
|||
l, r := 0, 0
|
||||
for i := 1; i < len(cells); i++ {
|
||||
if cells[i][0] == cells[r][0] && cells[i][1]-cells[r][1] > 1 {
|
||||
ref, _ := f.coordinatesToRangeRef(append(cells[l], cells[r]...))
|
||||
ref, _ := coordinatesToRangeRef(append(cells[l], cells[r]...))
|
||||
if l == r {
|
||||
ref, _ = CoordinatesToCellName(cells[l][0], cells[l][1])
|
||||
}
|
||||
|
@ -394,13 +435,18 @@ func (f *File) squashSqref(cells [][]int) []string {
|
|||
r++
|
||||
}
|
||||
}
|
||||
ref, _ := f.coordinatesToRangeRef(append(cells[l], cells[r]...))
|
||||
ref, _ := coordinatesToRangeRef(append(cells[l], cells[r]...))
|
||||
if l == r {
|
||||
ref, _ = CoordinatesToCellName(cells[l][0], cells[l][1])
|
||||
}
|
||||
return append(refs, ref)
|
||||
}
|
||||
|
||||
// isFormulaDataValidation returns whether the data validation rule is a formula.
|
||||
func (dv *xlsxInnerXML) isFormula() bool {
|
||||
return dv != nil && !(strings.HasPrefix(dv.Content, """) && strings.HasSuffix(dv.Content, """))
|
||||
}
|
||||
|
||||
// unescapeDataValidationFormula returns unescaped data validation formula.
|
||||
func unescapeDataValidationFormula(val string) string {
|
||||
if strings.HasPrefix(val, "\"") { // Text detection
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
package excelize
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
@ -104,6 +105,31 @@ func TestDataValidation(t *testing.T) {
|
|||
dataValidations, err = f.GetDataValidations("Sheet1")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []*DataValidation(nil), dataValidations)
|
||||
|
||||
// Test get data validations which storage in the extension lists
|
||||
f = NewFile()
|
||||
ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
|
||||
assert.True(t, ok)
|
||||
ws.(*xlsxWorksheet).ExtLst = &xlsxExtLst{Ext: fmt.Sprintf(`<ext uri="%s" xmlns:x14="%s"><x14:dataValidations><x14:dataValidation type="list" allowBlank="1"><x14:formula1><xm:f>Sheet1!$B$1:$B$5</xm:f></x14:formula1><xm:sqref>A7:B8</xm:sqref></x14:dataValidation></x14:dataValidations></ext>`, ExtURIDataValidations, NameSpaceSpreadSheetX14.Value)}
|
||||
dataValidations, err = f.GetDataValidations("Sheet1")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []*DataValidation{
|
||||
{
|
||||
AllowBlank: true,
|
||||
Type: "list",
|
||||
Formula1: "Sheet1!$B$1:$B$5",
|
||||
Sqref: "A7:B8",
|
||||
},
|
||||
}, dataValidations)
|
||||
|
||||
// Test get data validations with invalid extension list characters
|
||||
ws.(*xlsxWorksheet).ExtLst = &xlsxExtLst{Ext: fmt.Sprintf(`<ext uri="%s" xmlns:x14="%s"><x14:dataValidations></x14:dataValidation></x14:dataValidations></ext>`, ExtURIDataValidations, NameSpaceSpreadSheetX14.Value)}
|
||||
_, err = f.GetDataValidations("Sheet1")
|
||||
assert.EqualError(t, err, "XML syntax error on line 1: element <dataValidations> closed by </dataValidation>")
|
||||
|
||||
// Test get validations without validations
|
||||
assert.Nil(t, getDataValidations(nil))
|
||||
assert.Nil(t, getDataValidations(&xlsxDataValidations{DataValidation: []*xlsxDataValidation{nil}}))
|
||||
}
|
||||
|
||||
func TestDataValidationError(t *testing.T) {
|
||||
|
|
28
date.go
28
date.go
|
@ -214,3 +214,31 @@ func formatYear(y int) int {
|
|||
}
|
||||
return y
|
||||
}
|
||||
|
||||
// getDurationNumFmt returns most simplify numbers format code for time
|
||||
// duration type cell value by given worksheet name, cell reference and number.
|
||||
func getDurationNumFmt(d time.Duration) int {
|
||||
if d >= time.Hour*24 {
|
||||
return 46
|
||||
}
|
||||
// Whole minutes
|
||||
if d.Minutes() == float64(int(d.Minutes())) {
|
||||
return 20
|
||||
}
|
||||
return 21
|
||||
}
|
||||
|
||||
// getTimeNumFmt returns most simplify numbers format code for time type cell
|
||||
// value by given worksheet name, cell reference and number.
|
||||
func getTimeNumFmt(t time.Time) int {
|
||||
nextMonth := t.AddDate(0, 1, 0)
|
||||
// Whole months
|
||||
if t.Day() == 1 && nextMonth.Day() == 1 {
|
||||
return 17
|
||||
}
|
||||
// Whole days
|
||||
if t.Hour() == 0 && t.Minute() == 0 && t.Second() == 0 && t.Nanosecond() == 0 {
|
||||
return 14
|
||||
}
|
||||
return 22
|
||||
}
|
||||
|
|
582
drawing.go
582
drawing.go
|
@ -108,7 +108,7 @@ func (f *File) addChart(opts *Chart, comboCharts []*Chart) {
|
|||
},
|
||||
}
|
||||
xlsxChartSpace.SpPr = f.drawShapeFill(opts.Fill, xlsxChartSpace.SpPr)
|
||||
plotAreaFunc := map[ChartType]func(*Chart) *cPlotArea{
|
||||
plotAreaFunc := map[ChartType]func(pa *cPlotArea, opts *Chart) *cPlotArea{
|
||||
Area: f.drawBaseChart,
|
||||
AreaStacked: f.drawBaseChart,
|
||||
AreaPercentStacked: f.drawBaseChart,
|
||||
|
@ -176,14 +176,19 @@ func (f *File) addChart(opts *Chart, comboCharts []*Chart) {
|
|||
if field.IsNil() {
|
||||
continue
|
||||
}
|
||||
immutable.FieldByName(mutable.Type().Field(i).Name).Set(field)
|
||||
fld := immutable.FieldByName(mutable.Type().Field(i).Name)
|
||||
if field.Kind() == reflect.Slice && i < 16 { // All []*cCharts type fields
|
||||
fld.Set(reflect.Append(fld, field.Index(0)))
|
||||
continue
|
||||
}
|
||||
fld.Set(field)
|
||||
}
|
||||
}
|
||||
addChart(xlsxChartSpace.Chart.PlotArea, plotAreaFunc[opts.Type](opts))
|
||||
addChart(xlsxChartSpace.Chart.PlotArea, plotAreaFunc[opts.Type](xlsxChartSpace.Chart.PlotArea, opts))
|
||||
order := len(opts.Series)
|
||||
for idx := range comboCharts {
|
||||
comboCharts[idx].order = order
|
||||
addChart(xlsxChartSpace.Chart.PlotArea, plotAreaFunc[comboCharts[idx].Type](comboCharts[idx]))
|
||||
addChart(xlsxChartSpace.Chart.PlotArea, plotAreaFunc[comboCharts[idx].Type](xlsxChartSpace.Chart.PlotArea, comboCharts[idx]))
|
||||
order += len(comboCharts[idx].Series)
|
||||
}
|
||||
chart, _ := xml.Marshal(xlsxChartSpace)
|
||||
|
@ -193,240 +198,242 @@ func (f *File) addChart(opts *Chart, comboCharts []*Chart) {
|
|||
|
||||
// drawBaseChart provides a function to draw the c:plotArea element for bar,
|
||||
// and column series charts by given format sets.
|
||||
func (f *File) drawBaseChart(opts *Chart) *cPlotArea {
|
||||
c := cCharts{
|
||||
BarDir: &attrValString{
|
||||
Val: stringPtr("col"),
|
||||
func (f *File) drawBaseChart(pa *cPlotArea, opts *Chart) *cPlotArea {
|
||||
c := []*cCharts{
|
||||
{
|
||||
BarDir: &attrValString{
|
||||
Val: stringPtr("col"),
|
||||
},
|
||||
Grouping: &attrValString{
|
||||
Val: stringPtr(plotAreaChartGrouping[opts.Type]),
|
||||
},
|
||||
VaryColors: &attrValBool{
|
||||
Val: opts.VaryColors,
|
||||
},
|
||||
Ser: f.drawChartSeries(opts),
|
||||
Shape: f.drawChartShape(opts),
|
||||
DLbls: f.drawChartDLbls(opts),
|
||||
AxID: f.genAxID(opts),
|
||||
Overlap: &attrValInt{Val: intPtr(100)},
|
||||
},
|
||||
Grouping: &attrValString{
|
||||
Val: stringPtr(plotAreaChartGrouping[opts.Type]),
|
||||
},
|
||||
VaryColors: &attrValBool{
|
||||
Val: opts.VaryColors,
|
||||
},
|
||||
Ser: f.drawChartSeries(opts),
|
||||
Shape: f.drawChartShape(opts),
|
||||
DLbls: f.drawChartDLbls(opts),
|
||||
AxID: f.genAxID(opts),
|
||||
Overlap: &attrValInt{Val: intPtr(100)},
|
||||
}
|
||||
var ok bool
|
||||
if *c.BarDir.Val, ok = plotAreaChartBarDir[opts.Type]; !ok {
|
||||
c.BarDir = nil
|
||||
if *c[0].BarDir.Val, ok = plotAreaChartBarDir[opts.Type]; !ok {
|
||||
c[0].BarDir = nil
|
||||
}
|
||||
if *c.Overlap.Val, ok = plotAreaChartOverlap[opts.Type]; !ok {
|
||||
c.Overlap = nil
|
||||
if *c[0].Overlap.Val, ok = plotAreaChartOverlap[opts.Type]; !ok {
|
||||
c[0].Overlap = nil
|
||||
}
|
||||
catAx := f.drawPlotAreaCatAx(opts)
|
||||
valAx := f.drawPlotAreaValAx(opts)
|
||||
catAx := f.drawPlotAreaCatAx(pa, opts)
|
||||
valAx := f.drawPlotAreaValAx(pa, opts)
|
||||
charts := map[ChartType]*cPlotArea{
|
||||
Area: {
|
||||
AreaChart: &c,
|
||||
AreaChart: c,
|
||||
CatAx: catAx,
|
||||
ValAx: valAx,
|
||||
},
|
||||
AreaStacked: {
|
||||
AreaChart: &c,
|
||||
AreaChart: c,
|
||||
CatAx: catAx,
|
||||
ValAx: valAx,
|
||||
},
|
||||
AreaPercentStacked: {
|
||||
AreaChart: &c,
|
||||
AreaChart: c,
|
||||
CatAx: catAx,
|
||||
ValAx: valAx,
|
||||
},
|
||||
Area3D: {
|
||||
Area3DChart: &c,
|
||||
Area3DChart: c,
|
||||
CatAx: catAx,
|
||||
ValAx: valAx,
|
||||
},
|
||||
Area3DStacked: {
|
||||
Area3DChart: &c,
|
||||
Area3DChart: c,
|
||||
CatAx: catAx,
|
||||
ValAx: valAx,
|
||||
},
|
||||
Area3DPercentStacked: {
|
||||
Area3DChart: &c,
|
||||
Area3DChart: c,
|
||||
CatAx: catAx,
|
||||
ValAx: valAx,
|
||||
},
|
||||
Bar: {
|
||||
BarChart: &c,
|
||||
BarChart: c,
|
||||
CatAx: catAx,
|
||||
ValAx: valAx,
|
||||
},
|
||||
BarStacked: {
|
||||
BarChart: &c,
|
||||
BarChart: c,
|
||||
CatAx: catAx,
|
||||
ValAx: valAx,
|
||||
},
|
||||
BarPercentStacked: {
|
||||
BarChart: &c,
|
||||
BarChart: c,
|
||||
CatAx: catAx,
|
||||
ValAx: valAx,
|
||||
},
|
||||
Bar3DClustered: {
|
||||
Bar3DChart: &c,
|
||||
Bar3DChart: c,
|
||||
CatAx: catAx,
|
||||
ValAx: valAx,
|
||||
},
|
||||
Bar3DStacked: {
|
||||
Bar3DChart: &c,
|
||||
Bar3DChart: c,
|
||||
CatAx: catAx,
|
||||
ValAx: valAx,
|
||||
},
|
||||
Bar3DPercentStacked: {
|
||||
Bar3DChart: &c,
|
||||
Bar3DChart: c,
|
||||
CatAx: catAx,
|
||||
ValAx: valAx,
|
||||
},
|
||||
Bar3DConeClustered: {
|
||||
Bar3DChart: &c,
|
||||
Bar3DChart: c,
|
||||
CatAx: catAx,
|
||||
ValAx: valAx,
|
||||
},
|
||||
Bar3DConeStacked: {
|
||||
Bar3DChart: &c,
|
||||
Bar3DChart: c,
|
||||
CatAx: catAx,
|
||||
ValAx: valAx,
|
||||
},
|
||||
Bar3DConePercentStacked: {
|
||||
Bar3DChart: &c,
|
||||
Bar3DChart: c,
|
||||
CatAx: catAx,
|
||||
ValAx: valAx,
|
||||
},
|
||||
Bar3DPyramidClustered: {
|
||||
Bar3DChart: &c,
|
||||
Bar3DChart: c,
|
||||
CatAx: catAx,
|
||||
ValAx: valAx,
|
||||
},
|
||||
Bar3DPyramidStacked: {
|
||||
Bar3DChart: &c,
|
||||
Bar3DChart: c,
|
||||
CatAx: catAx,
|
||||
ValAx: valAx,
|
||||
},
|
||||
Bar3DPyramidPercentStacked: {
|
||||
Bar3DChart: &c,
|
||||
Bar3DChart: c,
|
||||
CatAx: catAx,
|
||||
ValAx: valAx,
|
||||
},
|
||||
Bar3DCylinderClustered: {
|
||||
Bar3DChart: &c,
|
||||
Bar3DChart: c,
|
||||
CatAx: catAx,
|
||||
ValAx: valAx,
|
||||
},
|
||||
Bar3DCylinderStacked: {
|
||||
Bar3DChart: &c,
|
||||
Bar3DChart: c,
|
||||
CatAx: catAx,
|
||||
ValAx: valAx,
|
||||
},
|
||||
Bar3DCylinderPercentStacked: {
|
||||
Bar3DChart: &c,
|
||||
Bar3DChart: c,
|
||||
CatAx: catAx,
|
||||
ValAx: valAx,
|
||||
},
|
||||
Col: {
|
||||
BarChart: &c,
|
||||
BarChart: c,
|
||||
CatAx: catAx,
|
||||
ValAx: valAx,
|
||||
},
|
||||
ColStacked: {
|
||||
BarChart: &c,
|
||||
BarChart: c,
|
||||
CatAx: catAx,
|
||||
ValAx: valAx,
|
||||
},
|
||||
ColPercentStacked: {
|
||||
BarChart: &c,
|
||||
BarChart: c,
|
||||
CatAx: catAx,
|
||||
ValAx: valAx,
|
||||
},
|
||||
Col3D: {
|
||||
Bar3DChart: &c,
|
||||
Bar3DChart: c,
|
||||
CatAx: catAx,
|
||||
ValAx: valAx,
|
||||
},
|
||||
Col3DClustered: {
|
||||
Bar3DChart: &c,
|
||||
Bar3DChart: c,
|
||||
CatAx: catAx,
|
||||
ValAx: valAx,
|
||||
},
|
||||
Col3DStacked: {
|
||||
Bar3DChart: &c,
|
||||
Bar3DChart: c,
|
||||
CatAx: catAx,
|
||||
ValAx: valAx,
|
||||
},
|
||||
Col3DPercentStacked: {
|
||||
Bar3DChart: &c,
|
||||
Bar3DChart: c,
|
||||
CatAx: catAx,
|
||||
ValAx: valAx,
|
||||
},
|
||||
Col3DCone: {
|
||||
Bar3DChart: &c,
|
||||
Bar3DChart: c,
|
||||
CatAx: catAx,
|
||||
ValAx: valAx,
|
||||
},
|
||||
Col3DConeClustered: {
|
||||
Bar3DChart: &c,
|
||||
Bar3DChart: c,
|
||||
CatAx: catAx,
|
||||
ValAx: valAx,
|
||||
},
|
||||
Col3DConeStacked: {
|
||||
Bar3DChart: &c,
|
||||
Bar3DChart: c,
|
||||
CatAx: catAx,
|
||||
ValAx: valAx,
|
||||
},
|
||||
Col3DConePercentStacked: {
|
||||
Bar3DChart: &c,
|
||||
Bar3DChart: c,
|
||||
CatAx: catAx,
|
||||
ValAx: valAx,
|
||||
},
|
||||
Col3DPyramid: {
|
||||
Bar3DChart: &c,
|
||||
Bar3DChart: c,
|
||||
CatAx: catAx,
|
||||
ValAx: valAx,
|
||||
},
|
||||
Col3DPyramidClustered: {
|
||||
Bar3DChart: &c,
|
||||
Bar3DChart: c,
|
||||
CatAx: catAx,
|
||||
ValAx: valAx,
|
||||
},
|
||||
Col3DPyramidStacked: {
|
||||
Bar3DChart: &c,
|
||||
Bar3DChart: c,
|
||||
CatAx: catAx,
|
||||
ValAx: valAx,
|
||||
},
|
||||
Col3DPyramidPercentStacked: {
|
||||
Bar3DChart: &c,
|
||||
Bar3DChart: c,
|
||||
CatAx: catAx,
|
||||
ValAx: valAx,
|
||||
},
|
||||
Col3DCylinder: {
|
||||
Bar3DChart: &c,
|
||||
Bar3DChart: c,
|
||||
CatAx: catAx,
|
||||
ValAx: valAx,
|
||||
},
|
||||
Col3DCylinderClustered: {
|
||||
Bar3DChart: &c,
|
||||
Bar3DChart: c,
|
||||
CatAx: catAx,
|
||||
ValAx: valAx,
|
||||
},
|
||||
Col3DCylinderStacked: {
|
||||
Bar3DChart: &c,
|
||||
Bar3DChart: c,
|
||||
CatAx: catAx,
|
||||
ValAx: valAx,
|
||||
},
|
||||
Col3DCylinderPercentStacked: {
|
||||
Bar3DChart: &c,
|
||||
Bar3DChart: c,
|
||||
CatAx: catAx,
|
||||
ValAx: valAx,
|
||||
},
|
||||
Bubble: {
|
||||
BubbleChart: &c,
|
||||
BubbleChart: c,
|
||||
CatAx: catAx,
|
||||
ValAx: valAx,
|
||||
},
|
||||
Bubble3D: {
|
||||
BubbleChart: &c,
|
||||
BubbleChart: c,
|
||||
CatAx: catAx,
|
||||
ValAx: valAx,
|
||||
},
|
||||
|
@ -436,233 +443,256 @@ func (f *File) drawBaseChart(opts *Chart) *cPlotArea {
|
|||
|
||||
// drawDoughnutChart provides a function to draw the c:plotArea element for
|
||||
// doughnut chart by given format sets.
|
||||
func (f *File) drawDoughnutChart(opts *Chart) *cPlotArea {
|
||||
func (f *File) drawDoughnutChart(pa *cPlotArea, opts *Chart) *cPlotArea {
|
||||
holeSize := 75
|
||||
if opts.HoleSize > 0 && opts.HoleSize <= 90 {
|
||||
holeSize = opts.HoleSize
|
||||
}
|
||||
|
||||
return &cPlotArea{
|
||||
DoughnutChart: &cCharts{
|
||||
VaryColors: &attrValBool{
|
||||
Val: opts.VaryColors,
|
||||
DoughnutChart: []*cCharts{
|
||||
{
|
||||
VaryColors: &attrValBool{
|
||||
Val: opts.VaryColors,
|
||||
},
|
||||
Ser: f.drawChartSeries(opts),
|
||||
HoleSize: &attrValInt{Val: intPtr(holeSize)},
|
||||
},
|
||||
Ser: f.drawChartSeries(opts),
|
||||
HoleSize: &attrValInt{Val: intPtr(holeSize)},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// drawLineChart provides a function to draw the c:plotArea element for line
|
||||
// chart by given format sets.
|
||||
func (f *File) drawLineChart(opts *Chart) *cPlotArea {
|
||||
func (f *File) drawLineChart(pa *cPlotArea, opts *Chart) *cPlotArea {
|
||||
return &cPlotArea{
|
||||
LineChart: &cCharts{
|
||||
Grouping: &attrValString{
|
||||
Val: stringPtr(plotAreaChartGrouping[opts.Type]),
|
||||
LineChart: []*cCharts{
|
||||
{
|
||||
Grouping: &attrValString{
|
||||
Val: stringPtr(plotAreaChartGrouping[opts.Type]),
|
||||
},
|
||||
VaryColors: &attrValBool{
|
||||
Val: boolPtr(false),
|
||||
},
|
||||
Ser: f.drawChartSeries(opts),
|
||||
DLbls: f.drawChartDLbls(opts),
|
||||
AxID: f.genAxID(opts),
|
||||
},
|
||||
VaryColors: &attrValBool{
|
||||
Val: boolPtr(false),
|
||||
},
|
||||
Ser: f.drawChartSeries(opts),
|
||||
DLbls: f.drawChartDLbls(opts),
|
||||
AxID: f.genAxID(opts),
|
||||
},
|
||||
CatAx: f.drawPlotAreaCatAx(opts),
|
||||
ValAx: f.drawPlotAreaValAx(opts),
|
||||
CatAx: f.drawPlotAreaCatAx(pa, opts),
|
||||
ValAx: f.drawPlotAreaValAx(pa, opts),
|
||||
}
|
||||
}
|
||||
|
||||
// drawLine3DChart provides a function to draw the c:plotArea element for line
|
||||
// chart by given format sets.
|
||||
func (f *File) drawLine3DChart(opts *Chart) *cPlotArea {
|
||||
func (f *File) drawLine3DChart(pa *cPlotArea, opts *Chart) *cPlotArea {
|
||||
return &cPlotArea{
|
||||
Line3DChart: &cCharts{
|
||||
Grouping: &attrValString{
|
||||
Val: stringPtr(plotAreaChartGrouping[opts.Type]),
|
||||
Line3DChart: []*cCharts{
|
||||
{
|
||||
Grouping: &attrValString{
|
||||
Val: stringPtr(plotAreaChartGrouping[opts.Type]),
|
||||
},
|
||||
VaryColors: &attrValBool{
|
||||
Val: boolPtr(false),
|
||||
},
|
||||
Ser: f.drawChartSeries(opts),
|
||||
DLbls: f.drawChartDLbls(opts),
|
||||
AxID: f.genAxID(opts),
|
||||
},
|
||||
VaryColors: &attrValBool{
|
||||
Val: boolPtr(false),
|
||||
},
|
||||
Ser: f.drawChartSeries(opts),
|
||||
DLbls: f.drawChartDLbls(opts),
|
||||
AxID: f.genAxID(opts),
|
||||
},
|
||||
CatAx: f.drawPlotAreaCatAx(opts),
|
||||
ValAx: f.drawPlotAreaValAx(opts),
|
||||
CatAx: f.drawPlotAreaCatAx(pa, opts),
|
||||
ValAx: f.drawPlotAreaValAx(pa, opts),
|
||||
}
|
||||
}
|
||||
|
||||
// drawPieChart provides a function to draw the c:plotArea element for pie
|
||||
// chart by given format sets.
|
||||
func (f *File) drawPieChart(opts *Chart) *cPlotArea {
|
||||
func (f *File) drawPieChart(pa *cPlotArea, opts *Chart) *cPlotArea {
|
||||
return &cPlotArea{
|
||||
PieChart: &cCharts{
|
||||
VaryColors: &attrValBool{
|
||||
Val: opts.VaryColors,
|
||||
PieChart: []*cCharts{
|
||||
{
|
||||
VaryColors: &attrValBool{
|
||||
Val: opts.VaryColors,
|
||||
},
|
||||
Ser: f.drawChartSeries(opts),
|
||||
},
|
||||
Ser: f.drawChartSeries(opts),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// drawPie3DChart provides a function to draw the c:plotArea element for 3D
|
||||
// pie chart by given format sets.
|
||||
func (f *File) drawPie3DChart(opts *Chart) *cPlotArea {
|
||||
func (f *File) drawPie3DChart(pa *cPlotArea, opts *Chart) *cPlotArea {
|
||||
return &cPlotArea{
|
||||
Pie3DChart: &cCharts{
|
||||
VaryColors: &attrValBool{
|
||||
Val: opts.VaryColors,
|
||||
Pie3DChart: []*cCharts{
|
||||
{
|
||||
VaryColors: &attrValBool{
|
||||
Val: opts.VaryColors,
|
||||
},
|
||||
Ser: f.drawChartSeries(opts),
|
||||
},
|
||||
Ser: f.drawChartSeries(opts),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// drawPieOfPieChart provides a function to draw the c:plotArea element for
|
||||
// pie chart by given format sets.
|
||||
func (f *File) drawPieOfPieChart(opts *Chart) *cPlotArea {
|
||||
func (f *File) drawPieOfPieChart(pa *cPlotArea, opts *Chart) *cPlotArea {
|
||||
var splitPos *attrValInt
|
||||
if opts.PlotArea.SecondPlotValues > 0 {
|
||||
splitPos = &attrValInt{Val: intPtr(opts.PlotArea.SecondPlotValues)}
|
||||
}
|
||||
return &cPlotArea{
|
||||
OfPieChart: &cCharts{
|
||||
OfPieType: &attrValString{
|
||||
Val: stringPtr("pie"),
|
||||
OfPieChart: []*cCharts{
|
||||
{
|
||||
OfPieType: &attrValString{
|
||||
Val: stringPtr("pie"),
|
||||
},
|
||||
VaryColors: &attrValBool{
|
||||
Val: opts.VaryColors,
|
||||
},
|
||||
Ser: f.drawChartSeries(opts),
|
||||
SplitPos: splitPos,
|
||||
SerLines: &attrValString{},
|
||||
},
|
||||
VaryColors: &attrValBool{
|
||||
Val: opts.VaryColors,
|
||||
},
|
||||
Ser: f.drawChartSeries(opts),
|
||||
SplitPos: splitPos,
|
||||
SerLines: &attrValString{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// drawBarOfPieChart provides a function to draw the c:plotArea element for
|
||||
// pie chart by given format sets.
|
||||
func (f *File) drawBarOfPieChart(opts *Chart) *cPlotArea {
|
||||
func (f *File) drawBarOfPieChart(pa *cPlotArea, opts *Chart) *cPlotArea {
|
||||
var splitPos *attrValInt
|
||||
if opts.PlotArea.SecondPlotValues > 0 {
|
||||
splitPos = &attrValInt{Val: intPtr(opts.PlotArea.SecondPlotValues)}
|
||||
}
|
||||
return &cPlotArea{
|
||||
OfPieChart: &cCharts{
|
||||
OfPieType: &attrValString{
|
||||
Val: stringPtr("bar"),
|
||||
OfPieChart: []*cCharts{
|
||||
{
|
||||
OfPieType: &attrValString{
|
||||
Val: stringPtr("bar"),
|
||||
},
|
||||
VaryColors: &attrValBool{
|
||||
Val: opts.VaryColors,
|
||||
},
|
||||
SplitPos: splitPos,
|
||||
Ser: f.drawChartSeries(opts),
|
||||
SerLines: &attrValString{},
|
||||
},
|
||||
VaryColors: &attrValBool{
|
||||
Val: opts.VaryColors,
|
||||
},
|
||||
SplitPos: splitPos,
|
||||
Ser: f.drawChartSeries(opts),
|
||||
SerLines: &attrValString{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// drawRadarChart provides a function to draw the c:plotArea element for radar
|
||||
// chart by given format sets.
|
||||
func (f *File) drawRadarChart(opts *Chart) *cPlotArea {
|
||||
func (f *File) drawRadarChart(pa *cPlotArea, opts *Chart) *cPlotArea {
|
||||
return &cPlotArea{
|
||||
RadarChart: &cCharts{
|
||||
RadarStyle: &attrValString{
|
||||
Val: stringPtr("marker"),
|
||||
RadarChart: []*cCharts{
|
||||
{
|
||||
RadarStyle: &attrValString{
|
||||
Val: stringPtr("marker"),
|
||||
},
|
||||
VaryColors: &attrValBool{
|
||||
Val: boolPtr(false),
|
||||
},
|
||||
Ser: f.drawChartSeries(opts),
|
||||
DLbls: f.drawChartDLbls(opts),
|
||||
AxID: f.genAxID(opts),
|
||||
},
|
||||
VaryColors: &attrValBool{
|
||||
Val: boolPtr(false),
|
||||
},
|
||||
Ser: f.drawChartSeries(opts),
|
||||
DLbls: f.drawChartDLbls(opts),
|
||||
AxID: f.genAxID(opts),
|
||||
},
|
||||
CatAx: f.drawPlotAreaCatAx(opts),
|
||||
ValAx: f.drawPlotAreaValAx(opts),
|
||||
CatAx: f.drawPlotAreaCatAx(pa, opts),
|
||||
ValAx: f.drawPlotAreaValAx(pa, opts),
|
||||
}
|
||||
}
|
||||
|
||||
// drawScatterChart provides a function to draw the c:plotArea element for
|
||||
// scatter chart by given format sets.
|
||||
func (f *File) drawScatterChart(opts *Chart) *cPlotArea {
|
||||
func (f *File) drawScatterChart(pa *cPlotArea, opts *Chart) *cPlotArea {
|
||||
return &cPlotArea{
|
||||
ScatterChart: &cCharts{
|
||||
ScatterStyle: &attrValString{
|
||||
Val: stringPtr("smoothMarker"), // line,lineMarker,marker,none,smooth,smoothMarker
|
||||
ScatterChart: []*cCharts{
|
||||
{
|
||||
ScatterStyle: &attrValString{
|
||||
Val: stringPtr("smoothMarker"), // line,lineMarker,marker,none,smooth,smoothMarker
|
||||
},
|
||||
VaryColors: &attrValBool{
|
||||
Val: boolPtr(false),
|
||||
},
|
||||
Ser: f.drawChartSeries(opts),
|
||||
DLbls: f.drawChartDLbls(opts),
|
||||
AxID: f.genAxID(opts),
|
||||
},
|
||||
VaryColors: &attrValBool{
|
||||
Val: boolPtr(false),
|
||||
},
|
||||
Ser: f.drawChartSeries(opts),
|
||||
DLbls: f.drawChartDLbls(opts),
|
||||
AxID: f.genAxID(opts),
|
||||
},
|
||||
CatAx: f.drawPlotAreaCatAx(opts),
|
||||
ValAx: f.drawPlotAreaValAx(opts),
|
||||
ValAx: append(f.drawPlotAreaCatAx(pa, opts), f.drawPlotAreaValAx(pa, opts)...),
|
||||
}
|
||||
}
|
||||
|
||||
// drawSurface3DChart provides a function to draw the c:surface3DChart element by
|
||||
// given format sets.
|
||||
func (f *File) drawSurface3DChart(opts *Chart) *cPlotArea {
|
||||
func (f *File) drawSurface3DChart(pa *cPlotArea, opts *Chart) *cPlotArea {
|
||||
plotArea := &cPlotArea{
|
||||
Surface3DChart: &cCharts{
|
||||
Ser: f.drawChartSeries(opts),
|
||||
AxID: []*attrValInt{
|
||||
{Val: intPtr(100000000)},
|
||||
{Val: intPtr(100000001)},
|
||||
{Val: intPtr(100000005)},
|
||||
Surface3DChart: []*cCharts{
|
||||
{
|
||||
Ser: f.drawChartSeries(opts),
|
||||
AxID: []*attrValInt{
|
||||
{Val: intPtr(100000000)},
|
||||
{Val: intPtr(100000001)},
|
||||
{Val: intPtr(100000005)},
|
||||
},
|
||||
},
|
||||
},
|
||||
CatAx: f.drawPlotAreaCatAx(opts),
|
||||
ValAx: f.drawPlotAreaValAx(opts),
|
||||
CatAx: f.drawPlotAreaCatAx(pa, opts),
|
||||
ValAx: f.drawPlotAreaValAx(pa, opts),
|
||||
SerAx: f.drawPlotAreaSerAx(opts),
|
||||
}
|
||||
if opts.Type == WireframeSurface3D {
|
||||
plotArea.Surface3DChart.Wireframe = &attrValBool{Val: boolPtr(true)}
|
||||
plotArea.Surface3DChart[0].Wireframe = &attrValBool{Val: boolPtr(true)}
|
||||
}
|
||||
return plotArea
|
||||
}
|
||||
|
||||
// drawSurfaceChart provides a function to draw the c:surfaceChart element by
|
||||
// given format sets.
|
||||
func (f *File) drawSurfaceChart(opts *Chart) *cPlotArea {
|
||||
func (f *File) drawSurfaceChart(pa *cPlotArea, opts *Chart) *cPlotArea {
|
||||
plotArea := &cPlotArea{
|
||||
SurfaceChart: &cCharts{
|
||||
Ser: f.drawChartSeries(opts),
|
||||
AxID: []*attrValInt{
|
||||
{Val: intPtr(100000000)},
|
||||
{Val: intPtr(100000001)},
|
||||
{Val: intPtr(100000005)},
|
||||
SurfaceChart: []*cCharts{
|
||||
{
|
||||
Ser: f.drawChartSeries(opts),
|
||||
AxID: []*attrValInt{
|
||||
{Val: intPtr(100000000)},
|
||||
{Val: intPtr(100000001)},
|
||||
{Val: intPtr(100000005)},
|
||||
},
|
||||
},
|
||||
},
|
||||
CatAx: f.drawPlotAreaCatAx(opts),
|
||||
ValAx: f.drawPlotAreaValAx(opts),
|
||||
CatAx: f.drawPlotAreaCatAx(pa, opts),
|
||||
ValAx: f.drawPlotAreaValAx(pa, opts),
|
||||
SerAx: f.drawPlotAreaSerAx(opts),
|
||||
}
|
||||
if opts.Type == WireframeContour {
|
||||
plotArea.SurfaceChart.Wireframe = &attrValBool{Val: boolPtr(true)}
|
||||
plotArea.SurfaceChart[0].Wireframe = &attrValBool{Val: boolPtr(true)}
|
||||
}
|
||||
return plotArea
|
||||
}
|
||||
|
||||
// drawBubbleChart provides a function to draw the c:bubbleChart element by
|
||||
// given format sets.
|
||||
func (f *File) drawBubbleChart(opts *Chart) *cPlotArea {
|
||||
func (f *File) drawBubbleChart(pa *cPlotArea, opts *Chart) *cPlotArea {
|
||||
plotArea := &cPlotArea{
|
||||
BubbleChart: &cCharts{
|
||||
VaryColors: &attrValBool{
|
||||
Val: opts.VaryColors,
|
||||
BubbleChart: []*cCharts{
|
||||
{
|
||||
VaryColors: &attrValBool{
|
||||
Val: opts.VaryColors,
|
||||
},
|
||||
Ser: f.drawChartSeries(opts),
|
||||
DLbls: f.drawChartDLbls(opts),
|
||||
AxID: f.genAxID(opts),
|
||||
},
|
||||
Ser: f.drawChartSeries(opts),
|
||||
DLbls: f.drawChartDLbls(opts),
|
||||
AxID: f.genAxID(opts),
|
||||
},
|
||||
ValAx: []*cAxs{f.drawPlotAreaCatAx(opts)[0], f.drawPlotAreaValAx(opts)[0]},
|
||||
ValAx: append(f.drawPlotAreaCatAx(pa, opts), f.drawPlotAreaValAx(pa, opts)...),
|
||||
}
|
||||
if opts.BubbleSize > 0 && opts.BubbleSize <= 300 {
|
||||
plotArea.BubbleChart.BubbleScale = &attrValFloat{Val: float64Ptr(float64(opts.BubbleSize))}
|
||||
plotArea.BubbleChart[0].BubbleScale = &attrValFloat{Val: float64Ptr(float64(opts.BubbleSize))}
|
||||
}
|
||||
return plotArea
|
||||
}
|
||||
|
@ -751,23 +781,19 @@ func (f *File) drawShapeFill(fill Fill, spPr *cSpPr) *cSpPr {
|
|||
func (f *File) drawChartSeriesSpPr(i int, opts *Chart) *cSpPr {
|
||||
spPr := &cSpPr{SolidFill: &aSolidFill{SchemeClr: &aSchemeClr{Val: "accent" + strconv.Itoa((opts.order+i)%6+1)}}}
|
||||
spPr = f.drawShapeFill(opts.Series[i].Fill, spPr)
|
||||
spPrScatter := &cSpPr{
|
||||
Ln: &aLn{
|
||||
W: 25400,
|
||||
NoFill: &attrValString{},
|
||||
},
|
||||
}
|
||||
spPrLine := &cSpPr{
|
||||
solid := &cSpPr{
|
||||
Ln: &aLn{
|
||||
W: f.ptToEMUs(opts.Series[i].Line.Width),
|
||||
Cap: "rnd", // rnd, sq, flat
|
||||
SolidFill: spPr.SolidFill,
|
||||
},
|
||||
}
|
||||
if chartSeriesSpPr, ok := map[ChartType]*cSpPr{
|
||||
Line: spPrLine, Scatter: spPrScatter,
|
||||
noLn := &cSpPr{Ln: &aLn{NoFill: &attrValString{}}}
|
||||
if chartSeriesSpPr, ok := map[ChartType]map[ChartLineType]*cSpPr{
|
||||
Line: {ChartLineUnset: solid, ChartLineSolid: solid, ChartLineNone: noLn, ChartLineAutomatic: solid},
|
||||
Scatter: {ChartLineUnset: noLn, ChartLineSolid: solid, ChartLineNone: noLn, ChartLineAutomatic: noLn},
|
||||
}[opts.Type]; ok {
|
||||
return chartSeriesSpPr
|
||||
return chartSeriesSpPr[opts.Series[i].Line.Type]
|
||||
}
|
||||
if spPr.SolidFill.SrgbClr != nil {
|
||||
return spPr
|
||||
|
@ -979,7 +1005,7 @@ func (f *File) drawChartSeriesDLbls(i int, opts *Chart) *cDLbls {
|
|||
}
|
||||
|
||||
// drawPlotAreaCatAx provides a function to draw the c:catAx element.
|
||||
func (f *File) drawPlotAreaCatAx(opts *Chart) []*cAxs {
|
||||
func (f *File) drawPlotAreaCatAx(pa *cPlotArea, opts *Chart) []*cAxs {
|
||||
maxVal := &attrValFloat{Val: opts.XAxis.Maximum}
|
||||
minVal := &attrValFloat{Val: opts.XAxis.Minimum}
|
||||
if opts.XAxis.Maximum == nil {
|
||||
|
@ -988,70 +1014,53 @@ func (f *File) drawPlotAreaCatAx(opts *Chart) []*cAxs {
|
|||
if opts.XAxis.Minimum == nil {
|
||||
minVal = nil
|
||||
}
|
||||
axs := []*cAxs{
|
||||
{
|
||||
AxID: &attrValInt{Val: intPtr(100000000)},
|
||||
Scaling: &cScaling{
|
||||
Orientation: &attrValString{Val: stringPtr(orientation[opts.XAxis.ReverseOrder])},
|
||||
Max: maxVal,
|
||||
Min: minVal,
|
||||
},
|
||||
Delete: &attrValBool{Val: boolPtr(opts.XAxis.None)},
|
||||
AxPos: &attrValString{Val: stringPtr(catAxPos[opts.XAxis.ReverseOrder])},
|
||||
NumFmt: &cNumFmt{FormatCode: "General"},
|
||||
MajorTickMark: &attrValString{Val: stringPtr("none")},
|
||||
MinorTickMark: &attrValString{Val: stringPtr("none")},
|
||||
Title: f.drawPlotAreaTitles(opts.XAxis.Title, ""),
|
||||
TickLblPos: &attrValString{Val: stringPtr("nextTo")},
|
||||
SpPr: f.drawPlotAreaSpPr(),
|
||||
TxPr: f.drawPlotAreaTxPr(&opts.YAxis),
|
||||
CrossAx: &attrValInt{Val: intPtr(100000001)},
|
||||
Crosses: &attrValString{Val: stringPtr("autoZero")},
|
||||
Auto: &attrValBool{Val: boolPtr(true)},
|
||||
LblAlgn: &attrValString{Val: stringPtr("ctr")},
|
||||
LblOffset: &attrValInt{Val: intPtr(100)},
|
||||
NoMultiLvlLbl: &attrValBool{Val: boolPtr(false)},
|
||||
ax := &cAxs{
|
||||
AxID: &attrValInt{Val: intPtr(100000000)},
|
||||
Scaling: &cScaling{
|
||||
Orientation: &attrValString{Val: stringPtr(orientation[opts.XAxis.ReverseOrder])},
|
||||
Max: maxVal,
|
||||
Min: minVal,
|
||||
},
|
||||
Delete: &attrValBool{Val: boolPtr(opts.XAxis.None)},
|
||||
AxPos: &attrValString{Val: stringPtr(catAxPos[opts.XAxis.ReverseOrder])},
|
||||
NumFmt: &cNumFmt{FormatCode: "General"},
|
||||
MajorTickMark: &attrValString{Val: stringPtr("none")},
|
||||
MinorTickMark: &attrValString{Val: stringPtr("none")},
|
||||
Title: f.drawPlotAreaTitles(opts.XAxis.Title, ""),
|
||||
TickLblPos: &attrValString{Val: stringPtr(tickLblPosVal[opts.XAxis.TickLabelPosition])},
|
||||
SpPr: f.drawPlotAreaSpPr(),
|
||||
TxPr: f.drawPlotAreaTxPr(&opts.XAxis),
|
||||
CrossAx: &attrValInt{Val: intPtr(100000001)},
|
||||
Crosses: &attrValString{Val: stringPtr("autoZero")},
|
||||
Auto: &attrValBool{Val: boolPtr(true)},
|
||||
LblAlgn: &attrValString{Val: stringPtr("ctr")},
|
||||
LblOffset: &attrValInt{Val: intPtr(100)},
|
||||
NoMultiLvlLbl: &attrValBool{Val: boolPtr(false)},
|
||||
}
|
||||
if numFmt := f.drawChartNumFmt(opts.XAxis.NumFmt); numFmt != nil {
|
||||
axs[0].NumFmt = numFmt
|
||||
ax.NumFmt = numFmt
|
||||
}
|
||||
if opts.XAxis.MajorGridLines {
|
||||
axs[0].MajorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()}
|
||||
ax.MajorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()}
|
||||
}
|
||||
if opts.XAxis.MinorGridLines {
|
||||
axs[0].MinorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()}
|
||||
ax.MinorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()}
|
||||
}
|
||||
if opts.XAxis.TickLabelSkip != 0 {
|
||||
axs[0].TickLblSkip = &attrValInt{Val: intPtr(opts.XAxis.TickLabelSkip)}
|
||||
ax.TickLblSkip = &attrValInt{Val: intPtr(opts.XAxis.TickLabelSkip)}
|
||||
}
|
||||
if opts.order > 0 && opts.YAxis.Secondary {
|
||||
axs = append(axs, &cAxs{
|
||||
AxID: &attrValInt{Val: intPtr(opts.XAxis.axID)},
|
||||
Scaling: &cScaling{
|
||||
Orientation: &attrValString{Val: stringPtr(orientation[opts.XAxis.ReverseOrder])},
|
||||
Max: maxVal,
|
||||
Min: minVal,
|
||||
},
|
||||
Delete: &attrValBool{Val: boolPtr(true)},
|
||||
AxPos: &attrValString{Val: stringPtr("b")},
|
||||
MajorTickMark: &attrValString{Val: stringPtr("none")},
|
||||
MinorTickMark: &attrValString{Val: stringPtr("none")},
|
||||
TickLblPos: &attrValString{Val: stringPtr("nextTo")},
|
||||
SpPr: f.drawPlotAreaSpPr(),
|
||||
TxPr: f.drawPlotAreaTxPr(&opts.YAxis),
|
||||
CrossAx: &attrValInt{Val: intPtr(opts.YAxis.axID)},
|
||||
Auto: &attrValBool{Val: boolPtr(true)},
|
||||
LblAlgn: &attrValString{Val: stringPtr("ctr")},
|
||||
LblOffset: &attrValInt{Val: intPtr(100)},
|
||||
NoMultiLvlLbl: &attrValBool{Val: boolPtr(false)},
|
||||
})
|
||||
if opts.order > 0 && opts.YAxis.Secondary && pa.CatAx != nil {
|
||||
ax.AxID = &attrValInt{Val: intPtr(opts.XAxis.axID)}
|
||||
ax.Delete = &attrValBool{Val: boolPtr(true)}
|
||||
ax.Crosses = nil
|
||||
ax.CrossAx = &attrValInt{Val: intPtr(opts.YAxis.axID)}
|
||||
return []*cAxs{pa.CatAx[0], ax}
|
||||
}
|
||||
return axs
|
||||
return []*cAxs{ax}
|
||||
}
|
||||
|
||||
// drawPlotAreaValAx provides a function to draw the c:valAx element.
|
||||
func (f *File) drawPlotAreaValAx(opts *Chart) []*cAxs {
|
||||
func (f *File) drawPlotAreaValAx(pa *cPlotArea, opts *Chart) []*cAxs {
|
||||
maxVal := &attrValFloat{Val: opts.YAxis.Maximum}
|
||||
minVal := &attrValFloat{Val: opts.YAxis.Minimum}
|
||||
if opts.YAxis.Maximum == nil {
|
||||
|
@ -1064,67 +1073,52 @@ func (f *File) drawPlotAreaValAx(opts *Chart) []*cAxs {
|
|||
if opts.YAxis.LogBase >= 2 && opts.YAxis.LogBase <= 1000 {
|
||||
logBase = &attrValFloat{Val: float64Ptr(opts.YAxis.LogBase)}
|
||||
}
|
||||
axs := []*cAxs{
|
||||
{
|
||||
AxID: &attrValInt{Val: intPtr(100000001)},
|
||||
Scaling: &cScaling{
|
||||
LogBase: logBase,
|
||||
Orientation: &attrValString{Val: stringPtr(orientation[opts.YAxis.ReverseOrder])},
|
||||
Max: maxVal,
|
||||
Min: minVal,
|
||||
},
|
||||
Delete: &attrValBool{Val: boolPtr(opts.YAxis.None)},
|
||||
AxPos: &attrValString{Val: stringPtr(valAxPos[opts.YAxis.ReverseOrder])},
|
||||
Title: f.drawPlotAreaTitles(opts.YAxis.Title, "horz"),
|
||||
NumFmt: &cNumFmt{
|
||||
FormatCode: chartValAxNumFmtFormatCode[opts.Type],
|
||||
},
|
||||
MajorTickMark: &attrValString{Val: stringPtr("none")},
|
||||
MinorTickMark: &attrValString{Val: stringPtr("none")},
|
||||
TickLblPos: &attrValString{Val: stringPtr("nextTo")},
|
||||
SpPr: f.drawPlotAreaSpPr(),
|
||||
TxPr: f.drawPlotAreaTxPr(&opts.XAxis),
|
||||
CrossAx: &attrValInt{Val: intPtr(100000000)},
|
||||
Crosses: &attrValString{Val: stringPtr("autoZero")},
|
||||
CrossBetween: &attrValString{Val: stringPtr(chartValAxCrossBetween[opts.Type])},
|
||||
ax := &cAxs{
|
||||
AxID: &attrValInt{Val: intPtr(100000001)},
|
||||
Scaling: &cScaling{
|
||||
LogBase: logBase,
|
||||
Orientation: &attrValString{Val: stringPtr(orientation[opts.YAxis.ReverseOrder])},
|
||||
Max: maxVal,
|
||||
Min: minVal,
|
||||
},
|
||||
Delete: &attrValBool{Val: boolPtr(opts.YAxis.None)},
|
||||
AxPos: &attrValString{Val: stringPtr(valAxPos[opts.YAxis.ReverseOrder])},
|
||||
Title: f.drawPlotAreaTitles(opts.YAxis.Title, "horz"),
|
||||
NumFmt: &cNumFmt{
|
||||
FormatCode: chartValAxNumFmtFormatCode[opts.Type],
|
||||
},
|
||||
MajorTickMark: &attrValString{Val: stringPtr("none")},
|
||||
MinorTickMark: &attrValString{Val: stringPtr("none")},
|
||||
TickLblPos: &attrValString{Val: stringPtr(tickLblPosVal[opts.YAxis.TickLabelPosition])},
|
||||
SpPr: f.drawPlotAreaSpPr(),
|
||||
TxPr: f.drawPlotAreaTxPr(&opts.YAxis),
|
||||
CrossAx: &attrValInt{Val: intPtr(100000000)},
|
||||
Crosses: &attrValString{Val: stringPtr("autoZero")},
|
||||
CrossBetween: &attrValString{Val: stringPtr(chartValAxCrossBetween[opts.Type])},
|
||||
}
|
||||
if numFmt := f.drawChartNumFmt(opts.YAxis.NumFmt); numFmt != nil {
|
||||
axs[0].NumFmt = numFmt
|
||||
ax.NumFmt = numFmt
|
||||
}
|
||||
if opts.YAxis.MajorGridLines {
|
||||
axs[0].MajorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()}
|
||||
ax.MajorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()}
|
||||
}
|
||||
if opts.YAxis.MinorGridLines {
|
||||
axs[0].MinorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()}
|
||||
ax.MinorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()}
|
||||
}
|
||||
if pos, ok := valTickLblPos[opts.Type]; ok {
|
||||
axs[0].TickLblPos.Val = stringPtr(pos)
|
||||
if pos, ok := tickLblPosNone[opts.Type]; ok {
|
||||
ax.TickLblPos.Val = stringPtr(pos)
|
||||
}
|
||||
if opts.YAxis.MajorUnit != 0 {
|
||||
axs[0].MajorUnit = &attrValFloat{Val: float64Ptr(opts.YAxis.MajorUnit)}
|
||||
ax.MajorUnit = &attrValFloat{Val: float64Ptr(opts.YAxis.MajorUnit)}
|
||||
}
|
||||
if opts.order > 0 && opts.YAxis.Secondary {
|
||||
axs = append(axs, &cAxs{
|
||||
AxID: &attrValInt{Val: intPtr(opts.YAxis.axID)},
|
||||
Scaling: &cScaling{
|
||||
Orientation: &attrValString{Val: stringPtr(orientation[opts.YAxis.ReverseOrder])},
|
||||
Max: maxVal,
|
||||
Min: minVal,
|
||||
},
|
||||
Delete: &attrValBool{Val: boolPtr(false)},
|
||||
AxPos: &attrValString{Val: stringPtr("r")},
|
||||
MajorTickMark: &attrValString{Val: stringPtr("none")},
|
||||
MinorTickMark: &attrValString{Val: stringPtr("none")},
|
||||
TickLblPos: &attrValString{Val: stringPtr("nextTo")},
|
||||
SpPr: f.drawPlotAreaSpPr(),
|
||||
TxPr: f.drawPlotAreaTxPr(&opts.XAxis),
|
||||
CrossAx: &attrValInt{Val: intPtr(opts.XAxis.axID)},
|
||||
Crosses: &attrValString{Val: stringPtr("max")},
|
||||
CrossBetween: &attrValString{Val: stringPtr(chartValAxCrossBetween[opts.Type])},
|
||||
})
|
||||
if opts.order > 0 && opts.YAxis.Secondary && pa.ValAx != nil {
|
||||
ax.AxID = &attrValInt{Val: intPtr(opts.YAxis.axID)}
|
||||
ax.AxPos = &attrValString{Val: stringPtr("r")}
|
||||
ax.Crosses = &attrValString{Val: stringPtr("max")}
|
||||
ax.CrossAx = &attrValInt{Val: intPtr(opts.XAxis.axID)}
|
||||
return []*cAxs{pa.ValAx[0], ax}
|
||||
}
|
||||
return axs
|
||||
return []*cAxs{ax}
|
||||
}
|
||||
|
||||
// drawPlotAreaSerAx provides a function to draw the c:serAx element.
|
||||
|
@ -1147,7 +1141,7 @@ func (f *File) drawPlotAreaSerAx(opts *Chart) []*cAxs {
|
|||
},
|
||||
Delete: &attrValBool{Val: boolPtr(opts.YAxis.None)},
|
||||
AxPos: &attrValString{Val: stringPtr(catAxPos[opts.XAxis.ReverseOrder])},
|
||||
TickLblPos: &attrValString{Val: stringPtr("nextTo")},
|
||||
TickLblPos: &attrValString{Val: stringPtr(tickLblPosVal[opts.YAxis.TickLabelPosition])},
|
||||
SpPr: f.drawPlotAreaSpPr(),
|
||||
TxPr: f.drawPlotAreaTxPr(nil),
|
||||
CrossAx: &attrValInt{Val: intPtr(100000001)},
|
||||
|
@ -1262,6 +1256,12 @@ func (f *File) drawPlotAreaTxPr(opts *ChartAxis) *cTxPr {
|
|||
}
|
||||
if opts != nil {
|
||||
drawChartFont(&opts.Font, &cTxPr.P.PPr.DefRPr)
|
||||
if -90 <= opts.Alignment.TextRotation && opts.Alignment.TextRotation <= 90 {
|
||||
cTxPr.BodyPr.Rot = opts.Alignment.TextRotation * 60000
|
||||
}
|
||||
if idx := inStrSlice(supportedDrawingTextVerticalType, opts.Alignment.Vertical, true); idx != -1 {
|
||||
cTxPr.BodyPr.Vert = supportedDrawingTextVerticalType[idx]
|
||||
}
|
||||
}
|
||||
return cTxPr
|
||||
}
|
||||
|
|
18
errors.go
18
errors.go
|
@ -88,6 +88,9 @@ var (
|
|||
// ErrOutlineLevel defined the error message on receive an invalid outline
|
||||
// level number.
|
||||
ErrOutlineLevel = errors.New("invalid outline level")
|
||||
// ErrPageSetupAdjustTo defined the error message for receiving a page setup
|
||||
// adjust to value exceeds limit.
|
||||
ErrPageSetupAdjustTo = errors.New("adjust to value must be between 10 and 400")
|
||||
// ErrParameterInvalid defined the error message on receive the invalid
|
||||
// parameter.
|
||||
ErrParameterInvalid = errors.New("parameter is invalid")
|
||||
|
@ -97,6 +100,9 @@ var (
|
|||
// ErrPasswordLengthInvalid defined the error message on invalid password
|
||||
// length.
|
||||
ErrPasswordLengthInvalid = errors.New("password length invalid")
|
||||
// ErrPivotTableClassicLayout defined the error message on enable
|
||||
// ClassicLayout and CompactData in the same time.
|
||||
ErrPivotTableClassicLayout = errors.New("cannot enable ClassicLayout and CompactData in the same time")
|
||||
// ErrSave defined the error message for saving file.
|
||||
ErrSave = errors.New("no path defined for file, consider File.WriteTo or File.Write")
|
||||
// ErrSheetIdx defined the error message on receive the invalid worksheet
|
||||
|
@ -246,6 +252,12 @@ func newInvalidNameError(name string) error {
|
|||
return fmt.Errorf("invalid name %q, the name should be starts with a letter or underscore, can not include a space or character, and can not conflict with an existing name in the workbook", name)
|
||||
}
|
||||
|
||||
// newInvalidPageLayoutValueError defined the error message on receiving the invalid
|
||||
// page layout options value.
|
||||
func newInvalidPageLayoutValueError(name, value, msg string) error {
|
||||
return fmt.Errorf("invalid %s value %q, acceptable value should be one of %s", name, value, msg)
|
||||
}
|
||||
|
||||
// newInvalidRowNumberError defined the error message on receiving the invalid
|
||||
// row number.
|
||||
func newInvalidRowNumberError(row int) error {
|
||||
|
@ -264,6 +276,12 @@ func newInvalidStyleID(styleID int) error {
|
|||
return fmt.Errorf("invalid style ID %d", styleID)
|
||||
}
|
||||
|
||||
// newNoExistSlicerError defined the error message on receiving the non existing
|
||||
// slicer name.
|
||||
func newNoExistSlicerError(name string) error {
|
||||
return fmt.Errorf("slicer %s does not exist", name)
|
||||
}
|
||||
|
||||
// newNoExistTableError defined the error message on receiving the non existing
|
||||
// table name.
|
||||
func newNoExistTableError(name string) error {
|
||||
|
|
218
excelize.go
218
excelize.go
|
@ -228,7 +228,7 @@ func (f *File) getOptions(opts ...Options) *Options {
|
|||
}
|
||||
|
||||
// CharsetTranscoder Set user defined codepage transcoder function for open
|
||||
// XLSX from non UTF-8 encoding.
|
||||
// workbook from non UTF-8 encoding.
|
||||
func (f *File) CharsetTranscoder(fn charsetTranscoderFn) *File { f.CharsetReader = fn; return f }
|
||||
|
||||
// Creates new XML decoder with charset reader.
|
||||
|
@ -242,15 +242,18 @@ func (f *File) xmlNewDecoder(rdr io.Reader) (ret *xml.Decoder) {
|
|||
// time.Time type cell value by given worksheet name, cell reference and
|
||||
// number format code.
|
||||
func (f *File) setDefaultTimeStyle(sheet, cell string, format int) error {
|
||||
s, err := f.GetCellStyle(sheet, cell)
|
||||
styleIdx, err := f.GetCellStyle(sheet, cell)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if s == 0 {
|
||||
style, _ := f.NewStyle(&Style{NumFmt: format})
|
||||
err = f.SetCellStyle(sheet, cell, cell, style)
|
||||
if styleIdx == 0 {
|
||||
styleIdx, _ = f.NewStyle(&Style{NumFmt: format})
|
||||
} else {
|
||||
style, _ := f.GetStyle(styleIdx)
|
||||
style.NumFmt = format
|
||||
styleIdx, _ = f.NewStyle(style)
|
||||
}
|
||||
return err
|
||||
return f.SetCellStyle(sheet, cell, cell, styleIdx)
|
||||
}
|
||||
|
||||
// workSheetReader provides a function to get the pointer to the structure
|
||||
|
@ -305,84 +308,85 @@ func (f *File) workSheetReader(sheet string) (ws *xlsxWorksheet, err error) {
|
|||
// checkSheet provides a function to fill each row element and make that is
|
||||
// continuous in a worksheet of XML.
|
||||
func (ws *xlsxWorksheet) checkSheet() {
|
||||
row, r0 := ws.checkSheetRows()
|
||||
sheetData := xlsxSheetData{Row: make([]xlsxRow, row)}
|
||||
row = 0
|
||||
for _, r := range ws.SheetData.Row {
|
||||
if r.R == nil {
|
||||
row++
|
||||
r.R = intPtr(row)
|
||||
sheetData.Row[row-1] = r
|
||||
continue
|
||||
}
|
||||
if *r.R == row && row > 0 {
|
||||
sheetData.Row[*r.R-1].C = append(sheetData.Row[*r.R-1].C, r.C...)
|
||||
continue
|
||||
}
|
||||
if *r.R != 0 {
|
||||
sheetData.Row[*r.R-1] = r
|
||||
row = *r.R
|
||||
}
|
||||
}
|
||||
for i := 1; i <= len(sheetData.Row); i++ {
|
||||
sheetData.Row[i-1].R = intPtr(i)
|
||||
}
|
||||
ws.checkSheetR0(&sheetData, r0)
|
||||
}
|
||||
|
||||
// checkSheetRows returns the last row number of the worksheet and rows element
|
||||
// with r="0" attribute.
|
||||
func (ws *xlsxWorksheet) checkSheetRows() (int, []xlsxRow) {
|
||||
var (
|
||||
row, maxVal int
|
||||
r0 []xlsxRow
|
||||
maxRowNum = func(num int, c []xlsxC) int {
|
||||
for _, cell := range c {
|
||||
if _, n, err := CellNameToCoordinates(cell.R); err == nil && n > num {
|
||||
num = n
|
||||
row int
|
||||
r0Rows []xlsxRow
|
||||
lastRowNum = func(r xlsxRow) int {
|
||||
var num int
|
||||
for _, cell := range r.C {
|
||||
if _, row, err := CellNameToCoordinates(cell.R); err == nil {
|
||||
if row > num {
|
||||
num = row
|
||||
}
|
||||
}
|
||||
}
|
||||
return num
|
||||
}
|
||||
)
|
||||
for i, r := range ws.SheetData.Row {
|
||||
if r.R == nil {
|
||||
row++
|
||||
continue
|
||||
}
|
||||
if i == 0 && *r.R == 0 {
|
||||
if num := maxRowNum(row, r.C); num > maxVal {
|
||||
maxVal = num
|
||||
for i := 0; i < len(ws.SheetData.Row); i++ {
|
||||
r := ws.SheetData.Row[i]
|
||||
if r.R == 0 || r.R == row {
|
||||
num := lastRowNum(r)
|
||||
if num > row {
|
||||
row = num
|
||||
}
|
||||
r0 = append(r0, r)
|
||||
if num == 0 {
|
||||
row++
|
||||
}
|
||||
r.R = row
|
||||
r0Rows = append(r0Rows, r)
|
||||
ws.SheetData.Row = append(ws.SheetData.Row[:i], ws.SheetData.Row[i+1:]...)
|
||||
i--
|
||||
continue
|
||||
}
|
||||
if *r.R != 0 && *r.R > row {
|
||||
row = *r.R
|
||||
if r.R != 0 && r.R > row {
|
||||
row = r.R
|
||||
}
|
||||
}
|
||||
if maxVal > row {
|
||||
row = maxVal
|
||||
sheetData := xlsxSheetData{Row: make([]xlsxRow, row)}
|
||||
row = 0
|
||||
for _, r := range ws.SheetData.Row {
|
||||
if r.R != 0 {
|
||||
sheetData.Row[r.R-1] = r
|
||||
row = r.R
|
||||
}
|
||||
}
|
||||
for _, r0Row := range r0Rows {
|
||||
sheetData.Row[r0Row.R-1].R = r0Row.R
|
||||
ws.checkSheetR0(&sheetData, &r0Row, true)
|
||||
}
|
||||
for i := 1; i <= row; i++ {
|
||||
sheetData.Row[i-1].R = i
|
||||
ws.checkSheetR0(&sheetData, &sheetData.Row[i-1], false)
|
||||
}
|
||||
return row, r0
|
||||
}
|
||||
|
||||
// checkSheetR0 handle the row element with r="0" attribute, cells in this row
|
||||
// could be disorderly, the cell in this row can be used as the value of
|
||||
// which cell is empty in the normal rows.
|
||||
func (ws *xlsxWorksheet) checkSheetR0(sheetData *xlsxSheetData, r0s []xlsxRow) {
|
||||
for _, r0 := range r0s {
|
||||
for _, cell := range r0.C {
|
||||
if col, row, err := CellNameToCoordinates(cell.R); err == nil {
|
||||
rowIdx := row - 1
|
||||
columns, colIdx := len(sheetData.Row[rowIdx].C), col-1
|
||||
for c := columns; c < col; c++ {
|
||||
sheetData.Row[rowIdx].C = append(sheetData.Row[rowIdx].C, xlsxC{})
|
||||
}
|
||||
if !sheetData.Row[rowIdx].C[colIdx].hasValue() {
|
||||
sheetData.Row[rowIdx].C[colIdx] = cell
|
||||
}
|
||||
}
|
||||
func (ws *xlsxWorksheet) checkSheetR0(sheetData *xlsxSheetData, rowData *xlsxRow, r0 bool) {
|
||||
checkRow := func(col, row int, r0 bool, cell xlsxC) {
|
||||
rowIdx := row - 1
|
||||
columns, colIdx := len(sheetData.Row[rowIdx].C), col-1
|
||||
for c := columns; c < col; c++ {
|
||||
sheetData.Row[rowIdx].C = append(sheetData.Row[rowIdx].C, xlsxC{})
|
||||
}
|
||||
if !sheetData.Row[rowIdx].C[colIdx].hasValue() {
|
||||
sheetData.Row[rowIdx].C[colIdx] = cell
|
||||
}
|
||||
if r0 {
|
||||
sheetData.Row[rowIdx].C[colIdx] = cell
|
||||
}
|
||||
}
|
||||
var err error
|
||||
for i, cell := range rowData.C {
|
||||
col, row := i+1, rowData.R
|
||||
if cell.R == "" {
|
||||
checkRow(col, row, r0, cell)
|
||||
continue
|
||||
}
|
||||
if col, row, err = CellNameToCoordinates(cell.R); err == nil && r0 {
|
||||
checkRow(col, row, r0, cell)
|
||||
}
|
||||
}
|
||||
ws.SheetData = *sheetData
|
||||
|
@ -452,14 +456,14 @@ func (f *File) addRels(relPath, relType, target, targetMode string) int {
|
|||
// UpdateLinkedValue fix linked values within a spreadsheet are not updating in
|
||||
// Office Excel application. This function will be remove value tag when met a
|
||||
// cell have a linked value. Reference
|
||||
// https://social.technet.microsoft.com/Forums/office/en-US/e16bae1f-6a2c-4325-8013-e989a3479066/excel-2010-linked-cells-not-updating
|
||||
// https://learn.microsoft.com/en-us/archive/msdn-technet-forums/e16bae1f-6a2c-4325-8013-e989a3479066
|
||||
//
|
||||
// Notice: after opening generated workbook, Excel will update the linked value
|
||||
// and generate a new value and will prompt to save the file or not.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// <row r="19" spans="2:2">
|
||||
// <row r="19">
|
||||
// <c r="B19">
|
||||
// <f>SUM(Sheet2!D2,Sheet2!D11)</f>
|
||||
// <v>100</v>
|
||||
|
@ -468,7 +472,7 @@ func (f *File) addRels(relPath, relType, target, targetMode string) int {
|
|||
//
|
||||
// to
|
||||
//
|
||||
// <row r="19" spans="2:2">
|
||||
// <row r="19">
|
||||
// <c r="B19">
|
||||
// <f>SUM(Sheet2!D2,Sheet2!D11)</f>
|
||||
// </c>
|
||||
|
@ -588,3 +592,77 @@ func (f *File) setContentTypePartProjectExtensions(contentType string) error {
|
|||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// metadataReader provides a function to get the pointer to the structure
|
||||
// after deserialization of xl/metadata.xml.
|
||||
func (f *File) metadataReader() (*xlsxMetadata, error) {
|
||||
var mataData xlsxMetadata
|
||||
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLMetadata)))).
|
||||
Decode(&mataData); err != nil && err != io.EOF {
|
||||
return &mataData, err
|
||||
}
|
||||
return &mataData, nil
|
||||
}
|
||||
|
||||
// richValueReader provides a function to get the pointer to the structure after
|
||||
// deserialization of xl/richData/richvalue.xml.
|
||||
func (f *File) richValueReader() (*xlsxRichValueData, error) {
|
||||
var richValue xlsxRichValueData
|
||||
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLRdRichValuePart)))).
|
||||
Decode(&richValue); err != nil && err != io.EOF {
|
||||
return &richValue, err
|
||||
}
|
||||
return &richValue, nil
|
||||
}
|
||||
|
||||
// richValueRelReader provides a function to get the pointer to the structure
|
||||
// after deserialization of xl/richData/richValueRel.xml.
|
||||
func (f *File) richValueRelReader() (*xlsxRichValueRels, error) {
|
||||
var richValueRels xlsxRichValueRels
|
||||
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLRdRichValueRel)))).
|
||||
Decode(&richValueRels); err != nil && err != io.EOF {
|
||||
return &richValueRels, err
|
||||
}
|
||||
return &richValueRels, nil
|
||||
}
|
||||
|
||||
// richValueWebImageReader provides a function to get the pointer to the
|
||||
// structure after deserialization of xl/richData/rdRichValueWebImage.xml.
|
||||
func (f *File) richValueWebImageReader() (*xlsxWebImagesSupportingRichData, error) {
|
||||
var richValueWebImages xlsxWebImagesSupportingRichData
|
||||
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLRdRichValueWebImagePart)))).
|
||||
Decode(&richValueWebImages); err != nil && err != io.EOF {
|
||||
return &richValueWebImages, err
|
||||
}
|
||||
return &richValueWebImages, nil
|
||||
}
|
||||
|
||||
// getRichDataRichValueRelRelationships provides a function to get relationships
|
||||
// from xl/richData/_rels/richValueRel.xml.rels by given relationship ID.
|
||||
func (f *File) getRichDataRichValueRelRelationships(rID string) *xlsxRelationship {
|
||||
if rels, _ := f.relsReader(defaultXMLRdRichValueRelRels); rels != nil {
|
||||
rels.mu.Lock()
|
||||
defer rels.mu.Unlock()
|
||||
for _, v := range rels.Relationships {
|
||||
if v.ID == rID {
|
||||
return &v
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getRichValueWebImageRelationships provides a function to get relationships
|
||||
// from xl/richData/_rels/rdRichValueWebImage.xml.rels by given relationship ID.
|
||||
func (f *File) getRichValueWebImageRelationships(rID string) *xlsxRelationship {
|
||||
if rels, _ := f.relsReader(defaultXMLRdRichValueWebImagePartRels); rels != nil {
|
||||
rels.mu.Lock()
|
||||
defer rels.mu.Unlock()
|
||||
for _, v := range rels.Relationships {
|
||||
if v.ID == rID {
|
||||
return &v
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -208,6 +208,30 @@ func TestSaveFile(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.NoError(t, f.Save())
|
||||
assert.NoError(t, f.Close())
|
||||
|
||||
t.Run("for_save_multiple_times", func(t *testing.T) {
|
||||
{
|
||||
f, err := OpenFile(filepath.Join("test", "TestSaveFile.xlsx"))
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, f.SetCellValue("Sheet1", "A20", 20))
|
||||
assert.NoError(t, f.Save())
|
||||
|
||||
assert.NoError(t, f.SetCellValue("Sheet1", "A21", 21))
|
||||
assert.NoError(t, f.Save())
|
||||
assert.NoError(t, f.Close())
|
||||
}
|
||||
{
|
||||
f, err := OpenFile(filepath.Join("test", "TestSaveFile.xlsx"))
|
||||
assert.NoError(t, err)
|
||||
val, err := f.GetCellValue("Sheet1", "A20")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "20", val)
|
||||
val, err = f.GetCellValue("Sheet1", "A21")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "21", val)
|
||||
assert.NoError(t, f.Close())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestSaveAsWrongPath(t *testing.T) {
|
||||
|
@ -365,11 +389,11 @@ func TestNewFile(t *testing.T) {
|
|||
f := NewFile()
|
||||
_, err := f.NewSheet("Sheet1")
|
||||
assert.NoError(t, err)
|
||||
_, err = f.NewSheet("XLSXSheet2")
|
||||
_, err = f.NewSheet("Sheet2")
|
||||
assert.NoError(t, err)
|
||||
_, err = f.NewSheet("XLSXSheet3")
|
||||
_, err = f.NewSheet("Sheet3")
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, f.SetCellInt("XLSXSheet2", "A23", 56))
|
||||
assert.NoError(t, f.SetCellInt("Sheet2", "A23", 56))
|
||||
assert.NoError(t, f.SetCellStr("Sheet1", "B20", "42"))
|
||||
f.SetActiveSheet(0)
|
||||
|
||||
|
@ -431,6 +455,18 @@ func TestSetCellHyperLink(t *testing.T) {
|
|||
assert.Equal(t, link, true)
|
||||
assert.Equal(t, "https://github.com/xuri/excelize", target)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Test remove hyperlink for a cell
|
||||
f = NewFile()
|
||||
assert.NoError(t, f.SetCellHyperLink("Sheet1", "A1", "Sheet1!D8", "Location"))
|
||||
ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml")
|
||||
assert.True(t, ok)
|
||||
ws.(*xlsxWorksheet).Hyperlinks.Hyperlink[0].Ref = "A1:D4"
|
||||
assert.NoError(t, f.SetCellHyperLink("Sheet1", "B2", "", "None"))
|
||||
// Test remove hyperlink for a cell with invalid cell reference
|
||||
assert.NoError(t, f.SetCellHyperLink("Sheet1", "A1", "Sheet1!D8", "Location"))
|
||||
ws.(*xlsxWorksheet).Hyperlinks.Hyperlink[0].Ref = "A:A"
|
||||
assert.Error(t, f.SetCellHyperLink("Sheet1", "B2", "", "None"), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")))
|
||||
}
|
||||
|
||||
func TestGetCellHyperLink(t *testing.T) {
|
||||
|
@ -742,11 +778,11 @@ func TestSetCellStyleNumberFormat(t *testing.T) {
|
|||
idxTbl := []int{0, 1, 2, 3, 4, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49}
|
||||
value := []string{"37947.7500001", "-37947.7500001", "0.007", "2.1", "String"}
|
||||
expected := [][]string{
|
||||
{"37947.7500001", "37948", "37947.75", "37,948", "37,947.75", "3794775%", "3794775.00%", "3.79E+04", "37947 3/4", "37947 3/4", "11-22-03", "22-Nov-03", "22-Nov", "Nov-03", "6:00 PM", "6:00:00 PM", "18:00", "18:00:00", "11/22/03 18:00", "37,948 ", "37,948 ", "37,947.75 ", "37,947.75 ", "37,948", "$37,948", "37,947.75", "$37,947.75", "00:00", "910746:00:00", "00:00.0", "37947.7500001", "37947.7500001"},
|
||||
{"-37947.7500001", "-37948", "-37947.75", "-37,948", "-37,947.75", "-3794775%", "-3794775.00%", "-3.79E+04", "-37947 3/4", "-37947 3/4", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "(37,948)", "(37,948)", "(37,947.75)", "(37,947.75)", "(37,948)", "$(37,948)", "(37,947.75)", "$(37,947.75)", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001"},
|
||||
{"0.007", "0", "0.01", "0", "0.01", "1%", "0.70%", "7.00E-03", "0 ", "0 ", "12-30-99", "30-Dec-99", "30-Dec", "Dec-99", "12:10 AM", "12:10:05 AM", "00:10", "00:10:05", "12/30/99 00:10", "0 ", "0 ", "0.01 ", "0.01 ", "0", "$0", "0.01", "$0.01", "10:05", "0:10:05", "10:04.8", "0.007", "0.007"},
|
||||
{"2.1", "2", "2.10", "2", "2.10", "210%", "210.00%", "2.10E+00", "2 1/9", "2 1/10", "01-01-00", "1-Jan-00", "1-Jan", "Jan-00", "2:24 AM", "2:24:00 AM", "02:24", "02:24:00", "1/1/00 02:24", "2 ", "2 ", "2.10 ", "2.10 ", "2", "$2", "2.10", "$2.10", "24:00", "50:24:00", "24:00.0", "2.1", "2.1"},
|
||||
{"String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String"},
|
||||
{"37947.75", "37948", "37947.75", "37,948", "37,947.75", "3794775%", "3794775.00%", "3.79E+04", "37947 3/4", "37947 3/4", "11-22-03", "22-Nov-03", "22-Nov", "Nov-03", "6:00 PM", "6:00:00 PM", "18:00", "18:00:00", "11/22/03 18:00", "37,948 ", "37,948 ", "37,947.75 ", "37,947.75 ", " 37,948 ", " $37,948 ", " 37,947.75 ", " $37,947.75 ", "00:00", "910746:00:00", "00:00.0", "37947.7500001", "37947.7500001"},
|
||||
{"-37947.75", "-37948", "-37947.75", "-37,948", "-37,947.75", "-3794775%", "-3794775.00%", "-3.79E+04", "-37947 3/4", "-37947 3/4", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "(37,948)", "(37,948)", "(37,947.75)", "(37,947.75)", " (37,948)", " $(37,948)", " (37,947.75)", " $(37,947.75)", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001"},
|
||||
{"0.007", "0", "0.01", "0", "0.01", "1%", "0.70%", "7.00E-03", "0 ", "0 ", "12-30-99", "30-Dec-99", "30-Dec", "Dec-99", "12:10 AM", "12:10:05 AM", "00:10", "00:10:05", "12/30/99 00:10", "0 ", "0 ", "0.01 ", "0.01 ", " 0 ", " $0 ", " 0.01 ", " $0.01 ", "10:05", "0:10:05", "10:04.8", "0.007", "0.007"},
|
||||
{"2.1", "2", "2.10", "2", "2.10", "210%", "210.00%", "2.10E+00", "2 1/9", "2 1/10", "01-01-00", "1-Jan-00", "1-Jan", "Jan-00", "2:24 AM", "2:24:00 AM", "02:24", "02:24:00", "1/1/00 02:24", "2 ", "2 ", "2.10 ", "2.10 ", " 2 ", " $2 ", " 2.10 ", " $2.10 ", "24:00", "50:24:00", "24:00.0", "2.1", "2.1"},
|
||||
{"String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", " String ", " String ", " String ", " String ", "String", "String", "String", "String", "String"},
|
||||
}
|
||||
|
||||
for c, v := range value {
|
||||
|
@ -834,11 +870,17 @@ func TestSetCellStyleCurrencyNumberFormat(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestSetCellStyleLangNumberFormat(t *testing.T) {
|
||||
rawCellValues := [][]string{{"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}}
|
||||
rawCellValues := make([][]string, 42)
|
||||
for i := 0; i < 42; i++ {
|
||||
rawCellValues[i] = []string{"45162"}
|
||||
}
|
||||
for lang, expected := range map[CultureName][][]string{
|
||||
CultureNameUnknown: rawCellValues,
|
||||
CultureNameEnUS: {{"8/24/23"}, {"8/24/23"}, {"8/24/23"}, {"8/24/23"}, {"8/24/23"}, {"0:00:00"}, {"0:00:00"}, {"0:00:00"}, {"0:00:00"}, {"45162"}, {"8/24/23"}, {"8/24/23"}, {"8/24/23"}, {"8/24/23"}, {"8/24/23"}, {"8/24/23"}, {"8/24/23"}, {"8/24/23"}, {"8/24/23"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}},
|
||||
CultureNameJaJP: {{"R5.8.24"}, {"令和5年8月24日"}, {"令和5年8月24日"}, {"8/24/23"}, {"2023年8月24日"}, {"0時00分"}, {"0時00分00秒"}, {"2023年8月"}, {"8月24日"}, {"R5.8.24"}, {"R5.8.24"}, {"令和5年8月24日"}, {"2023年8月"}, {"8月24日"}, {"令和5年8月24日"}, {"2023年8月"}, {"8月24日"}, {"R5.8.24"}, {"令和5年8月24日"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}},
|
||||
CultureNameKoKR: {{"4356年 08月 24日"}, {"08-24"}, {"08-24"}, {"08-24-56"}, {"4356년 08월 24일"}, {"0시 00분"}, {"0시 00분 00초"}, {"4356-08-24"}, {"4356-08-24"}, {"4356年 08月 24日"}, {"4356年 08月 24日"}, {"08-24"}, {"4356-08-24"}, {"4356-08-24"}, {"08-24"}, {"4356-08-24"}, {"4356-08-24"}, {"4356年 08月 24日"}, {"08-24"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}},
|
||||
CultureNameZhCN: {{"2023年8月"}, {"8月24日"}, {"8月24日"}, {"8/24/23"}, {"2023年8月24日"}, {"0时00分"}, {"0时00分00秒"}, {"上午12时00分"}, {"上午12时00分00秒"}, {"2023年8月"}, {"2023年8月"}, {"8月24日"}, {"2023年8月"}, {"8月24日"}, {"8月24日"}, {"上午12时00分"}, {"上午12时00分00秒"}, {"2023年8月"}, {"8月24日"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}},
|
||||
CultureNameZhTW: {{"112/8/24"}, {"112年8月24日"}, {"112年8月24日"}, {"8/24/23"}, {"2023年8月24日"}, {"00時00分"}, {"00時00分00秒"}, {"上午12時00分"}, {"上午12時00分00秒"}, {"112/8/24"}, {"112/8/24"}, {"112年8月24日"}, {"上午12時00分"}, {"上午12時00分00秒"}, {"112年8月24日"}, {"上午12時00分"}, {"上午12時00分00秒"}, {"112/8/24"}, {"112年8月24日"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}},
|
||||
} {
|
||||
f, err := prepareTestBook5(Options{CultureInfo: lang})
|
||||
assert.NoError(t, err)
|
||||
|
@ -850,7 +892,10 @@ func TestSetCellStyleLangNumberFormat(t *testing.T) {
|
|||
// Test apply language number format code with date and time pattern
|
||||
for lang, expected := range map[CultureName][][]string{
|
||||
CultureNameEnUS: {{"2023-8-24"}, {"2023-8-24"}, {"2023-8-24"}, {"2023-8-24"}, {"2023-8-24"}, {"00:00:00"}, {"00:00:00"}, {"00:00:00"}, {"00:00:00"}, {"45162"}, {"2023-8-24"}, {"2023-8-24"}, {"2023-8-24"}, {"2023-8-24"}, {"2023-8-24"}, {"2023-8-24"}, {"2023-8-24"}, {"2023-8-24"}, {"2023-8-24"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}},
|
||||
CultureNameJaJP: {{"R5.8.24"}, {"令和5年8月24日"}, {"令和5年8月24日"}, {"2023-8-24"}, {"2023年8月24日"}, {"00:00:00"}, {"00:00:00"}, {"2023年8月"}, {"8月24日"}, {"R5.8.24"}, {"R5.8.24"}, {"令和5年8月24日"}, {"2023年8月"}, {"8月24日"}, {"令和5年8月24日"}, {"2023年8月"}, {"8月24日"}, {"R5.8.24"}, {"令和5年8月24日"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}},
|
||||
CultureNameKoKR: {{"4356年 08月 24日"}, {"08-24"}, {"08-24"}, {"4356-8-24"}, {"4356년 08월 24일"}, {"00:00:00"}, {"00:00:00"}, {"4356-08-24"}, {"4356-08-24"}, {"4356年 08月 24日"}, {"4356年 08月 24日"}, {"08-24"}, {"4356-08-24"}, {"4356-08-24"}, {"08-24"}, {"4356-08-24"}, {"4356-08-24"}, {"4356年 08月 24日"}, {"08-24"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}},
|
||||
CultureNameZhCN: {{"2023年8月"}, {"8月24日"}, {"8月24日"}, {"2023-8-24"}, {"2023年8月24日"}, {"00:00:00"}, {"00:00:00"}, {"上午12时00分"}, {"上午12时00分00秒"}, {"2023年8月"}, {"2023年8月"}, {"8月24日"}, {"2023年8月"}, {"8月24日"}, {"8月24日"}, {"上午12时00分"}, {"上午12时00分00秒"}, {"2023年8月"}, {"8月24日"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}},
|
||||
CultureNameZhTW: {{"112/8/24"}, {"112年8月24日"}, {"112年8月24日"}, {"2023-8-24"}, {"2023年8月24日"}, {"00:00:00"}, {"00:00:00"}, {"上午12時00分"}, {"上午12時00分00秒"}, {"112/8/24"}, {"112/8/24"}, {"112年8月24日"}, {"上午12時00分"}, {"上午12時00分00秒"}, {"112年8月24日"}, {"上午12時00分"}, {"上午12時00分00秒"}, {"112/8/24"}, {"112年8月24日"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}},
|
||||
} {
|
||||
f, err := prepareTestBook5(Options{CultureInfo: lang, ShortDatePattern: "yyyy-M-d", LongTimePattern: "hh:mm:ss"})
|
||||
assert.NoError(t, err)
|
||||
|
@ -962,7 +1007,7 @@ func TestSetDeleteSheet(t *testing.T) {
|
|||
f, err := prepareTestBook3()
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.NoError(t, f.DeleteSheet("XLSXSheet3"))
|
||||
assert.NoError(t, f.DeleteSheet("Sheet3"))
|
||||
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetDeleteSheet.TestBook3.xlsx")))
|
||||
})
|
||||
|
||||
|
@ -1088,7 +1133,7 @@ func TestConditionalFormat(t *testing.T) {
|
|||
{
|
||||
Type: "cell",
|
||||
Criteria: "between",
|
||||
Format: format1,
|
||||
Format: &format1,
|
||||
MinValue: "6",
|
||||
MaxValue: "8",
|
||||
},
|
||||
|
@ -1100,7 +1145,7 @@ func TestConditionalFormat(t *testing.T) {
|
|||
{
|
||||
Type: "cell",
|
||||
Criteria: ">",
|
||||
Format: format3,
|
||||
Format: &format3,
|
||||
Value: "6",
|
||||
},
|
||||
},
|
||||
|
@ -1111,7 +1156,7 @@ func TestConditionalFormat(t *testing.T) {
|
|||
{
|
||||
Type: "top",
|
||||
Criteria: "=",
|
||||
Format: format3,
|
||||
Format: &format3,
|
||||
},
|
||||
},
|
||||
))
|
||||
|
@ -1121,7 +1166,7 @@ func TestConditionalFormat(t *testing.T) {
|
|||
{
|
||||
Type: "unique",
|
||||
Criteria: "=",
|
||||
Format: format2,
|
||||
Format: &format2,
|
||||
},
|
||||
},
|
||||
))
|
||||
|
@ -1131,7 +1176,7 @@ func TestConditionalFormat(t *testing.T) {
|
|||
{
|
||||
Type: "duplicate",
|
||||
Criteria: "=",
|
||||
Format: format2,
|
||||
Format: &format2,
|
||||
},
|
||||
},
|
||||
))
|
||||
|
@ -1141,7 +1186,7 @@ func TestConditionalFormat(t *testing.T) {
|
|||
{
|
||||
Type: "top",
|
||||
Criteria: "=",
|
||||
Format: format1,
|
||||
Format: &format1,
|
||||
Value: "6",
|
||||
Percent: true,
|
||||
},
|
||||
|
@ -1153,7 +1198,7 @@ func TestConditionalFormat(t *testing.T) {
|
|||
{
|
||||
Type: "average",
|
||||
Criteria: "=",
|
||||
Format: format3,
|
||||
Format: &format3,
|
||||
AboveAverage: true,
|
||||
},
|
||||
},
|
||||
|
@ -1164,7 +1209,7 @@ func TestConditionalFormat(t *testing.T) {
|
|||
{
|
||||
Type: "average",
|
||||
Criteria: "=",
|
||||
Format: format1,
|
||||
Format: &format1,
|
||||
AboveAverage: false,
|
||||
},
|
||||
},
|
||||
|
@ -1187,7 +1232,7 @@ func TestConditionalFormat(t *testing.T) {
|
|||
{
|
||||
Type: "formula",
|
||||
Criteria: "L2<3",
|
||||
Format: format1,
|
||||
Format: &format1,
|
||||
},
|
||||
},
|
||||
))
|
||||
|
@ -1197,7 +1242,7 @@ func TestConditionalFormat(t *testing.T) {
|
|||
{
|
||||
Type: "cell",
|
||||
Criteria: ">",
|
||||
Format: format4,
|
||||
Format: &format4,
|
||||
Value: "0",
|
||||
},
|
||||
},
|
||||
|
@ -1610,13 +1655,13 @@ func prepareTestBook1() (*File, error) {
|
|||
|
||||
func prepareTestBook3() (*File, error) {
|
||||
f := NewFile()
|
||||
if _, err := f.NewSheet("XLSXSheet2"); err != nil {
|
||||
if _, err := f.NewSheet("Sheet2"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := f.NewSheet("XLSXSheet3"); err != nil {
|
||||
if _, err := f.NewSheet("Sheet3"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := f.SetCellInt("XLSXSheet2", "A23", 56); err != nil {
|
||||
if err := f.SetCellInt("Sheet2", "A23", 56); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := f.SetCellStr("Sheet1", "B20", "42"); err != nil {
|
||||
|
|
4
file.go
4
file.go
|
@ -212,7 +212,7 @@ func (f *File) writeToZip(zw *zip.Writer) error {
|
|||
files = append(files, path.(string))
|
||||
return true
|
||||
})
|
||||
sort.Strings(files)
|
||||
sort.Sort(sort.Reverse(sort.StringSlice(files)))
|
||||
for _, path := range files {
|
||||
var fi io.Writer
|
||||
if fi, err = zw.Create(path); err != nil {
|
||||
|
@ -228,7 +228,7 @@ func (f *File) writeToZip(zw *zip.Writer) error {
|
|||
tempFiles = append(tempFiles, path.(string))
|
||||
return true
|
||||
})
|
||||
sort.Strings(tempFiles)
|
||||
sort.Sort(sort.Reverse(sort.StringSlice(tempFiles)))
|
||||
for _, path := range tempFiles {
|
||||
var fi io.Writer
|
||||
if fi, err = zw.Create(path); err != nil {
|
||||
|
|
18
go.mod
18
go.mod
|
@ -3,20 +3,20 @@ module github.com/xuri/excelize/v2
|
|||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
|
||||
github.com/richardlehane/mscfb v1.0.4
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/xuri/efp v0.0.0-20231025114914-d1ff6096ae53
|
||||
github.com/xuri/nfp v0.0.0-20230919160717-d98342af3f05
|
||||
golang.org/x/crypto v0.19.0
|
||||
golang.org/x/image v0.14.0
|
||||
golang.org/x/net v0.21.0
|
||||
golang.org/x/text v0.14.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/tiendc/go-deepcopy v1.1.0
|
||||
github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d
|
||||
github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7
|
||||
golang.org/x/crypto v0.29.0
|
||||
golang.org/x/image v0.18.0
|
||||
golang.org/x/net v0.31.0
|
||||
golang.org/x/text v0.20.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/richardlehane/msoleps v1.0.3 // indirect
|
||||
github.com/richardlehane/msoleps v1.0.4 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
36
go.sum
36
go.sum
|
@ -1,28 +1,28 @@
|
|||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM=
|
||||
github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk=
|
||||
github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
||||
github.com/richardlehane/msoleps v1.0.3 h1:aznSZzrwYRl3rLKRT3gUk9am7T/mLNSnJINvN0AQoVM=
|
||||
github.com/richardlehane/msoleps v1.0.3/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/xuri/efp v0.0.0-20231025114914-d1ff6096ae53 h1:Chd9DkqERQQuHpXjR/HSV1jLZA6uaoiwwH3vSuF3IW0=
|
||||
github.com/xuri/efp v0.0.0-20231025114914-d1ff6096ae53/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
|
||||
github.com/xuri/nfp v0.0.0-20230919160717-d98342af3f05 h1:qhbILQo1K3mphbwKh1vNm4oGezE1eF9fQWmNiIpSfI4=
|
||||
github.com/xuri/nfp v0.0.0-20230919160717-d98342af3f05/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
|
||||
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4=
|
||||
golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
||||
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
github.com/richardlehane/msoleps v1.0.4 h1:WuESlvhX3gH2IHcd8UqyCuFY5yiq/GR/yqaSM/9/g00=
|
||||
github.com/richardlehane/msoleps v1.0.4/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tiendc/go-deepcopy v1.1.0 h1:rBHhm5vg7WYnGLwktbQouodWjBXDoStOL4S7v/K8S4A=
|
||||
github.com/tiendc/go-deepcopy v1.1.0/go.mod h1:toXoeQoUqXOOS/X4sKuiAoSk6elIdqc0pN7MTgOOo2I=
|
||||
github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d h1:llb0neMWDQe87IzJLS4Ci7psK/lVsjIS2otl+1WyRyY=
|
||||
github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
|
||||
github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7 h1:hPVCafDV85blFTabnqKgNhDCkJX25eik94Si9cTER4A=
|
||||
github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
|
||||
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
|
||||
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
|
||||
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
|
||||
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
|
||||
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
|
||||
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
|
||||
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
|
||||
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
|
26
lib.go
26
lib.go
|
@ -232,12 +232,18 @@ func ColumnNumberToName(num int) (string, error) {
|
|||
if num < MinColumns || num > MaxColumns {
|
||||
return "", ErrColumnNumber
|
||||
}
|
||||
var col string
|
||||
estimatedLength := 0
|
||||
for n := num; n > 0; n = (n - 1) / 26 {
|
||||
estimatedLength++
|
||||
}
|
||||
|
||||
result := make([]byte, estimatedLength)
|
||||
for num > 0 {
|
||||
col = string(rune((num-1)%26+65)) + col
|
||||
estimatedLength--
|
||||
result[estimatedLength] = byte((num-1)%26 + 'A')
|
||||
num = (num - 1) / 26
|
||||
}
|
||||
return col, nil
|
||||
return string(result), nil
|
||||
}
|
||||
|
||||
// CellNameToCoordinates converts alphanumeric cell name to [X, Y] coordinates
|
||||
|
@ -323,7 +329,7 @@ func sortCoordinates(coordinates []int) error {
|
|||
|
||||
// coordinatesToRangeRef provides a function to convert a pair of coordinates
|
||||
// to range reference.
|
||||
func (f *File) coordinatesToRangeRef(coordinates []int, abs ...bool) (string, error) {
|
||||
func coordinatesToRangeRef(coordinates []int, abs ...bool) (string, error) {
|
||||
if len(coordinates) != 4 {
|
||||
return "", ErrCoordinates
|
||||
}
|
||||
|
@ -360,7 +366,7 @@ func (f *File) getDefinedNameRefTo(definedNameName, currentSheet string) (refTo
|
|||
}
|
||||
|
||||
// flatSqref convert reference sequence to cell reference list.
|
||||
func (f *File) flatSqref(sqref string) (cells map[int][][]int, err error) {
|
||||
func flatSqref(sqref string) (cells map[int][][]int, err error) {
|
||||
var coordinates []int
|
||||
cells = make(map[int][][]int)
|
||||
for _, ref := range strings.Fields(sqref) {
|
||||
|
@ -646,6 +652,16 @@ func getRootElement(d *xml.Decoder) []xml.Attr {
|
|||
case xml.StartElement:
|
||||
tokenIdx++
|
||||
if tokenIdx == 1 {
|
||||
var ns bool
|
||||
for i := 0; i < len(startElement.Attr); i++ {
|
||||
if startElement.Attr[i].Value == NameSpaceSpreadSheet.Value &&
|
||||
startElement.Attr[i].Name == NameSpaceSpreadSheet.Name {
|
||||
ns = true
|
||||
}
|
||||
}
|
||||
if !ns {
|
||||
startElement.Attr = append(startElement.Attr, NameSpaceSpreadSheet)
|
||||
}
|
||||
return startElement.Attr
|
||||
}
|
||||
}
|
||||
|
|
13
lib_test.go
13
lib_test.go
|
@ -218,14 +218,13 @@ func TestCoordinatesToCellName_Error(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCoordinatesToRangeRef(t *testing.T) {
|
||||
f := NewFile()
|
||||
_, err := f.coordinatesToRangeRef([]int{})
|
||||
_, err := coordinatesToRangeRef([]int{})
|
||||
assert.EqualError(t, err, ErrCoordinates.Error())
|
||||
_, err = f.coordinatesToRangeRef([]int{1, -1, 1, 1})
|
||||
_, err = coordinatesToRangeRef([]int{1, -1, 1, 1})
|
||||
assert.Equal(t, newCoordinatesToCellNameError(1, -1), err)
|
||||
_, err = f.coordinatesToRangeRef([]int{1, 1, 1, -1})
|
||||
_, err = coordinatesToRangeRef([]int{1, 1, 1, -1})
|
||||
assert.Equal(t, newCoordinatesToCellNameError(1, -1), err)
|
||||
ref, err := f.coordinatesToRangeRef([]int{1, 1, 1, 1})
|
||||
ref, err := coordinatesToRangeRef([]int{1, 1, 1, 1})
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, ref, "A1:A1")
|
||||
}
|
||||
|
@ -290,6 +289,10 @@ func TestBytesReplace(t *testing.T) {
|
|||
|
||||
func TestGetRootElement(t *testing.T) {
|
||||
assert.Len(t, getRootElement(xml.NewDecoder(strings.NewReader(""))), 0)
|
||||
// Test get workbook root element which all workbook XML namespace has prefix
|
||||
f := NewFile()
|
||||
d := f.xmlNewDecoder(bytes.NewReader([]byte(`<x:workbook xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:x="http://schemas.openxmlformats.org/spreadsheetml/2006/main"></x:workbook>`)))
|
||||
assert.Len(t, getRootElement(d), 3)
|
||||
}
|
||||
|
||||
func TestSetIgnorableNameSpace(t *testing.T) {
|
||||
|
|
15
merge.go
15
merge.go
|
@ -66,6 +66,17 @@ func (f *File) MergeCell(sheet, topLeftCell, bottomRightCell string) error {
|
|||
}
|
||||
ws.mu.Lock()
|
||||
defer ws.mu.Unlock()
|
||||
for col := rect[0]; col <= rect[2]; col++ {
|
||||
for row := rect[1]; row <= rect[3]; row++ {
|
||||
if col == rect[0] && row == rect[1] {
|
||||
continue
|
||||
}
|
||||
ws.prepareSheetXML(col, row)
|
||||
c := &ws.SheetData.Row[row-1].C[col-1]
|
||||
c.setCellDefault("")
|
||||
_ = f.removeFormula(c, ws, sheet)
|
||||
}
|
||||
}
|
||||
ref := topLeftCell + ":" + bottomRightCell
|
||||
if ws.MergeCells != nil {
|
||||
ws.MergeCells.Cells = append(ws.MergeCells.Cells, &xlsxMergeCell{Ref: ref, rect: rect})
|
||||
|
@ -128,8 +139,8 @@ func (f *File) UnmergeCell(sheet, topLeftCell, bottomRightCell string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// GetMergeCells provides a function to get all merged cells from a worksheet
|
||||
// currently.
|
||||
// GetMergeCells provides a function to get all merged cells from a specific
|
||||
// worksheet.
|
||||
func (f *File) GetMergeCells(sheet string) ([]MergeCell, error) {
|
||||
var mergeCells []MergeCell
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
|
|
|
@ -11,6 +11,10 @@ func TestNumFmt(t *testing.T) {
|
|||
for _, item := range [][]string{
|
||||
{"123", "general", "123"},
|
||||
{"-123", ";general", "-123"},
|
||||
{"12345678901", "General", "12345678901"},
|
||||
{"43543.5448726851", "General", "43543.54487"},
|
||||
{"-43543.5448726851", "General", "-43543.54487"},
|
||||
{"1234567890.12345", "General", "1234567890"},
|
||||
{"43528", "y", "19"},
|
||||
{"43528", "Y", "19"},
|
||||
{"43528", "yy", "19"},
|
||||
|
@ -66,7 +70,10 @@ func TestNumFmt(t *testing.T) {
|
|||
{"0.97952546296296295", "h:m", "23:30"},
|
||||
{"43528", "mmmm", "March"},
|
||||
{"43528", "dddd", "Monday"},
|
||||
{"0", ";;;", "0"},
|
||||
{"0", ";;;", ""},
|
||||
{"0", "0%", "0%"},
|
||||
{"0", "0.0%", "0.0%"},
|
||||
{"0", "0.00%", "0.00%"},
|
||||
{"43528", "[$-409]MM/DD/YYYY", "03/04/2019"},
|
||||
{"43528", "[$-409]MM/DD/YYYY am/pm", "03/04/2019 AM"},
|
||||
{"43528", "[$-111]MM/DD/YYYY", "43528"},
|
||||
|
@ -76,22 +83,62 @@ func TestNumFmt(t *testing.T) {
|
|||
{"text", "AM/PM h h:mm", "text"},
|
||||
{"43466.189571759256", "[$-404]aaa;@", "週二"},
|
||||
{"43466.189571759256", "[$-404]aaaa;@", "星期二"},
|
||||
{"43466.189571759256", "[$-zh-TW]aaa;@", "週二"},
|
||||
{"43466.189571759256", "[$-zh-TW]aaaa;@", "星期二"},
|
||||
{"43466.189571759256", "[$-804]aaa;@", "周二"},
|
||||
{"43466.189571759256", "[$-804]aaaa;@", "星期二"},
|
||||
{"43466.189571759256", "[$-0804]aaa;@", "周二"},
|
||||
{"43466.189571759256", "[$-0804]aaaa;@", "星期二"},
|
||||
{"43466.189571759256", "[$-zh-CN]aaa;@", "周二"},
|
||||
{"43466.189571759256", "[$-zh-CN]aaaa;@", "星期二"},
|
||||
{"43466.189571759256", "[$-435]aaa;@", "Bi."},
|
||||
{"43466.189571759256", "[$-435]aaaa;@", "ULwesibili"},
|
||||
{"43466.189571759256", "[$-0435]aaa;@", "Bi."},
|
||||
{"43466.189571759256", "[$-0435]aaaa;@", "ULwesibili"},
|
||||
{"43466.189571759256", "[$-zu-ZA]aaa;@", "Bi."},
|
||||
{"43466.189571759256", "[$-zu-ZA]aaaa;@", "ULwesibili"},
|
||||
{"43466.189571759256", "[$-404]ddd;@", "週二"},
|
||||
{"43466.189571759256", "[$-404]dddd;@", "星期二"},
|
||||
{"43466.189571759256", "[$-0404]ddd;@", "週二"},
|
||||
{"43466.189571759256", "[$-0404]dddd;@", "星期二"},
|
||||
{"43466.189571759256", "[$-zh-TW]ddd;@", "週二"},
|
||||
{"43466.189571759256", "[$-zh-TW]dddd;@", "星期二"},
|
||||
{"43466.189571759256", "[$-804]ddd;@", "周二"},
|
||||
{"43466.189571759256", "[$-804]dddd;@", "星期二"},
|
||||
{"43466.189571759256", "[$-0804]ddd;@", "周二"},
|
||||
{"43466.189571759256", "[$-0804]dddd;@", "星期二"},
|
||||
{"43466.189571759256", "[$-zh-CN]ddd;@", "周二"},
|
||||
{"43466.189571759256", "[$-zh-CN]dddd;@", "星期二"},
|
||||
{"43466.189571759256", "[$-435]ddd;@", "Bi."},
|
||||
{"43466.189571759256", "[$-435]dddd;@", "ULwesibili"},
|
||||
{"43466.189571759256", "[$-0435]ddd;@", "Bi."},
|
||||
{"43466.189571759256", "[$-0435]dddd;@", "ULwesibili"},
|
||||
{"43466.189571759256", "[$-zu-ZA]ddd;@", "Bi."},
|
||||
{"43466.189571759256", "[$-zu-ZA]dddd;@", "ULwesibili"},
|
||||
{"44562.189571759256", "[$-36]mmm dd yyyy h:mm AM/PM d", "Jan. 01 2022 4:32 vm. 1"},
|
||||
{"44562.189571759256", "[$-36]mmmm dd yyyy h:mm AM/PM dd", "Januarie 01 2022 4:32 vm. 01"},
|
||||
{"44562.189571759256", "[$-36]mmmmm dd yyyy h:mm AM/PM ddd", "J 01 2022 4:32 vm. Sa."},
|
||||
{"44682.18957170139", "[$-36]mmm dd yyyy h:mm AM/PM dddd", "Mei 01 2022 4:32 vm. Sondag"},
|
||||
{"44682.18957170139", "[$-36]mmmm dd yyyy h:mm AM/PM aaa", "Mei 01 2022 4:32 vm. So."},
|
||||
{"44682.18957170139", "[$-36]mmmmm dd yyyy h:mm AM/PM aaaa", "M 01 2022 4:32 vm. Sondag"},
|
||||
{"44562.189571759256", "[$-036]mmm dd yyyy h:mm AM/PM d", "Jan. 01 2022 4:32 vm. 1"},
|
||||
{"44562.189571759256", "[$-036]mmmm dd yyyy h:mm AM/PM dd", "Januarie 01 2022 4:32 vm. 01"},
|
||||
{"44562.189571759256", "[$-036]mmmmm dd yyyy h:mm AM/PM ddd", "J 01 2022 4:32 vm. Sa."},
|
||||
{"44682.18957170139", "[$-036]mmm dd yyyy h:mm AM/PM dddd", "Mei 01 2022 4:32 vm. Sondag"},
|
||||
{"44682.18957170139", "[$-036]mmmm dd yyyy h:mm AM/PM aaa", "Mei 01 2022 4:32 vm. So."},
|
||||
{"44682.18957170139", "[$-036]mmmmm dd yyyy h:mm AM/PM aaaa", "M 01 2022 4:32 vm. Sondag"},
|
||||
{"44562.189571759256", "[$-0036]mmm dd yyyy h:mm AM/PM d", "Jan. 01 2022 4:32 vm. 1"},
|
||||
{"44562.189571759256", "[$-0036]mmmm dd yyyy h:mm AM/PM dd", "Januarie 01 2022 4:32 vm. 01"},
|
||||
{"44562.189571759256", "[$-0036]mmmmm dd yyyy h:mm AM/PM ddd", "J 01 2022 4:32 vm. Sa."},
|
||||
{"44682.18957170139", "[$-0036]mmm dd yyyy h:mm AM/PM dddd", "Mei 01 2022 4:32 vm. Sondag"},
|
||||
{"44682.18957170139", "[$-0036]mmmm dd yyyy h:mm AM/PM aaa", "Mei 01 2022 4:32 vm. So."},
|
||||
{"44682.18957170139", "[$-0036]mmmmm dd yyyy h:mm AM/PM aaaa", "M 01 2022 4:32 vm. Sondag"},
|
||||
{"44562.189571759256", "[$-af]mmm dd yyyy h:mm AM/PM d", "Jan. 01 2022 4:32 vm. 1"},
|
||||
{"44562.189571759256", "[$-af]mmmm dd yyyy h:mm AM/PM dd", "Januarie 01 2022 4:32 vm. 01"},
|
||||
{"44562.189571759256", "[$-af]mmmmm dd yyyy h:mm AM/PM ddd", "J 01 2022 4:32 vm. Sa."},
|
||||
{"44682.18957170139", "[$-af]mmm dd yyyy h:mm AM/PM dddd", "Mei 01 2022 4:32 vm. Sondag"},
|
||||
{"44682.18957170139", "[$-af]mmmm dd yyyy h:mm AM/PM aaa", "Mei 01 2022 4:32 vm. So."},
|
||||
{"44682.18957170139", "[$-af]mmmmm dd yyyy h:mm AM/PM aaaa", "M 01 2022 4:32 vm. Sondag"},
|
||||
{"44562.189571759256", "[$-436]mmm dd yyyy h:mm AM/PM d", "Jan. 01 2022 4:32 vm. 1"},
|
||||
{"44562.189571759256", "[$-436]mmmm dd yyyy h:mm AM/PM dd", "Januarie 01 2022 4:32 vm. 01"},
|
||||
{"44562.189571759256", "[$-436]mmmmm dd yyyy h:mm AM/PM ddd", "J 01 2022 4:32 vm. Sa."},
|
||||
|
@ -242,6 +289,28 @@ func TestNumFmt(t *testing.T) {
|
|||
{"43543.503206018519", "[$-401]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
|
||||
{"43543.503206018519", "[$-401]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
|
||||
{"43543.503206018519", "[$-401]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
|
||||
{"43466.189571759256", "[$-404]g\"年\"m\"月\"d\"日\";@", "\u6c11\u570b\u5e74\u0031\u6708\u0031\u65e5"},
|
||||
{"43466.189571759256", "[$-404]e\"年\"m\"月\"d\"日\";@", "\u0031\u0030\u0038\u5e74\u0031\u6708\u0031\u65e5"},
|
||||
{"43466.189571759256", "[$-404]ge\"年\"m\"月\"d\"日\";@", "\u6c11\u570b\u0031\u0030\u0038\u5e74\u0031\u6708\u0031\u65e5"},
|
||||
{"43466.189571759256", "[$-404]gge\"年\"m\"月\"d\"日\";@", "\u6c11\u570b\u0031\u0030\u0038\u5e74\u0031\u6708\u0031\u65e5"},
|
||||
{"43466.189571759256", "[$-404]ggge\"年\"m\"月\"d\"日\";@", "\u4e2d\u83ef\u6c11\u570b\u0031\u0030\u0038\u5e74\u0031\u6708\u0031\u65e5"},
|
||||
{"43466.189571759256", "[$-404]gggge\"年\"m\"月\"d\"日\";@", "\u4e2d\u83ef\u6c11\u570b\u0031\u0030\u0038\u5e74\u0031\u6708\u0031\u65e5"},
|
||||
{"4385.5083333333332", "[$-404]ge\"年\"m\"月\"d\"日\";@", "\u6c11\u570b\u5143\u5e74\u0031\u6708\u0032\u65e5"},
|
||||
{"4385.5083333333332", "[$-404]gge\"年\"m\"月\"d\"日\";@", "\u6c11\u570b\u5143\u5e74\u0031\u6708\u0032\u65e5"},
|
||||
{"4385.5083333333332", "[$-404]ggge\"年\"m\"月\"d\"日\";@", "\u4e2d\u83ef\u6c11\u570b\u5143\u5e74\u0031\u6708\u0032\u65e5"},
|
||||
{"4385.5083333333332", "[$-404]gggge\"年\"m\"月\"d\"日\";@", "\u4e2d\u83ef\u6c11\u570b\u5143\u5e74\u0031\u6708\u0032\u65e5"},
|
||||
{"123", "[$-404]ge\"年\"m\"月\"d\"日\";@", "\u6c11\u570b\u524d\u0031\u0032\u5e74\u0035\u6708\u0032\u65e5"},
|
||||
{"123", "[$-404]gge\"年\"m\"月\"d\"日\";@", "\u6c11\u570b\u524d\u0031\u0032\u5e74\u0035\u6708\u0032\u65e5"},
|
||||
{"123", "[$-404]ggge\"年\"m\"月\"d\"日\";@", "\u4e2d\u83ef\u6c11\u570b\u524d\u0031\u0032\u5e74\u0035\u6708\u0032\u65e5"},
|
||||
{"123", "[$-404]gggge\"年\"m\"月\"d\"日\";@", "\u4e2d\u83ef\u6c11\u570b\u524d\u0031\u0032\u5e74\u0035\u6708\u0032\u65e5"},
|
||||
{"44562.189571759256", "[$-1010401]mmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"},
|
||||
{"44562.189571759256", "[$-1010401]mmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"},
|
||||
{"44562.189571759256", "[$-1010401]mmmmm dd yyyy h:mm AM/PM", "\u064A 01 2022 4:32 \u0635"},
|
||||
{"44562.189571759256", "[$-1010401]mmmmmm dd yyyy h:mm AM/PM", "\u064A\u0646\u0627\u064A\u0631 01 2022 4:32 \u0635"},
|
||||
{"43543.503206018519", "[$-1010401]mmm dd yyyy h:mm AM/PM", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645"},
|
||||
{"43543.503206018519", "[$-1010401]mmmm dd yyyy h:mm AM/PM aaa", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
|
||||
{"43543.503206018519", "[$-1010401]mmmmm dd yyyy h:mm AM/PM ddd", "\u0645 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
|
||||
{"43543.503206018519", "[$-1010401]mmmmmm dd yyyy h:mm AM/PM dddd", "\u0645\u0627\u0631\u0633 19 2019 12:04 \u0645 \u0627\u0644\u062B\u0644\u0627\u062B\u0627\u0621"},
|
||||
{"44562.189571759256", "[$-2801]mmm dd yyyy h:mm AM/PM", "\u0643\u0627\u0646\u0648\u0646%A0\u0627\u0644\u062B\u0627\u0646\u064A 01 2022 4:32 \u0635"},
|
||||
{"44562.189571759256", "[$-2801]mmmm dd yyyy h:mm AM/PM", "\u0643\u0627\u0646\u0648\u0646%A0\u0627\u0644\u062B\u0627\u0646\u064A 01 2022 4:32 \u0635"},
|
||||
{"44562.189571759256", "[$-2801]mmmmm dd yyyy h:mm AM/PM", "\u0643 01 2022 4:32 \u0635"},
|
||||
|
@ -2667,6 +2736,9 @@ func TestNumFmt(t *testing.T) {
|
|||
{"44835.18957170139", "[$-41E]mmmmm dd yyyy h:mm AM/PM aaa", "\u0e15 01 2022 4:32 AM \u0E2A."},
|
||||
{"44866.18957170139", "[$-41E]mmmmm dd yyyy h:mm AM/PM ddd", "\u0e1e 01 2022 4:32 AM \u0E2D."},
|
||||
{"44896.18957170139", "[$-41E]mmmmm dd yyyy h:mm AM/PM dddd", "\u0e18 01 2022 4:32 AM \u0E1E\u0E24\u0E2B\u0E31\u0E2A\u0E1A\u0E14\u0E35"},
|
||||
{"100", "g\"年\"m\"月\"d\"日\";@", "年4月9日"},
|
||||
{"100", "e\"年\"m\"月\"d\"日\";@", "1900年4月9日"},
|
||||
{"100", "ge\"年\"m\"月\"d\"日\";@", "1900年4月9日"},
|
||||
{"100", "[$-411]ge\"年\"m\"月\"d\"日\";@", "1900年4月9日"},
|
||||
{"43709", "[$-411]ge\"年\"m\"月\"d\"日\";@", "R1年9月1日"},
|
||||
{"43709", "[$-411]gge\"年\"m\"月\"d\"日\";@", "\u4EE41年9月1日"},
|
||||
|
@ -3488,15 +3560,18 @@ func TestNumFmt(t *testing.T) {
|
|||
{"43543.503206018519", "[$-F400]h:mm:ss AM/PM", "12:04:37 PM"},
|
||||
{"text_", "General", "text_"},
|
||||
{"text_", "\"=====\"@@@\"--\"@\"----\"", "=====text_text_text_--text_----"},
|
||||
{"0.0450685976001E+21", "0_);[Red]\\(0\\)", "45068597600100000000"},
|
||||
{"8.0450685976001E+21", "0_);[Red]\\(0\\)", "8045068597600100000000"},
|
||||
{"8.0450685976001E-21", "0_);[Red]\\(0\\)", "0"},
|
||||
{"8.04506", "0_);[Red]\\(0\\)", "8"},
|
||||
{"0.0450685976001E+21", "0_);[Red]\\(0\\)", "45068597600100000000 "},
|
||||
{"8.0450685976001E+21", "0_);[Red]\\(0\\)", "8045068597600100000000 "},
|
||||
{"8.0450685976001E-21", "0_);[Red]\\(0\\)", "0 "},
|
||||
{"8.04506", "0_);[Red]\\(0\\)", "8 "},
|
||||
{"-0.0450685976001E+21", "0_);[Red]\\(0\\)", "(45068597600100000000)"},
|
||||
{"-8.0450685976001E+21", "0_);[Red]\\(0\\)", "(8045068597600100000000)"},
|
||||
{"-8.0450685976001E-21", "0_);[Red]\\(0\\)", "(0)"},
|
||||
{"-8.04506", "0_);[Red]\\(0\\)", "(8)"},
|
||||
{"-8.04506", "$#,##0.00_);[Red]($#,##0.00)", "($8.05)"},
|
||||
{"43543.5448726851", `_("$"* #,##0.00_);_("$"* \(#,##0.00\);_("$"* "-"??_);_(@_)`, " $43,543.54 "},
|
||||
{"1234.5678", "0", "1235"},
|
||||
{"1234.125", "0.00", "1234.13"},
|
||||
{"1234.5678", "0.00", "1234.57"},
|
||||
{"1234.5678", "#,##0", "1,235"},
|
||||
{"1234.5678", "#,##0.00", "1,234.57"},
|
||||
|
@ -3580,7 +3655,9 @@ func TestNumFmt(t *testing.T) {
|
|||
// Test format number with specified date and time format code
|
||||
for _, item := range [][]string{
|
||||
{"43543.503206018519", "[$-F800]dddd, mmmm dd, yyyy", "2019年3月19日"},
|
||||
{"43543.503206018519", "[$-x-sysdate]dddd, mmmm dd, yyyy", "2019年3月19日"},
|
||||
{"43543.503206018519", "[$-F400]h:mm:ss AM/PM", "12:04:37"},
|
||||
{"43543.503206018519", "[$-x-systime]h:mm:ss AM/PM", "12:04:37"},
|
||||
} {
|
||||
result := format(item[0], item[1], false, CellTypeNumber, &Options{
|
||||
ShortDatePattern: "yyyy/m/d",
|
||||
|
|
202
picture.go
202
picture.go
|
@ -23,6 +23,18 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
// PictureInsertType defines the type of the picture has been inserted into the
|
||||
// worksheet.
|
||||
type PictureInsertType int
|
||||
|
||||
// Insert picture types.
|
||||
const (
|
||||
PictureInsertTypePlaceOverCells PictureInsertType = iota
|
||||
PictureInsertTypePlaceInCell
|
||||
PictureInsertTypeIMAGE
|
||||
PictureInsertTypeDISPIMG
|
||||
)
|
||||
|
||||
// parseGraphicOptions provides a function to parse the format settings of
|
||||
// the picture with default value.
|
||||
func parseGraphicOptions(opts *GraphicOptions) *GraphicOptions {
|
||||
|
@ -52,7 +64,10 @@ func parseGraphicOptions(opts *GraphicOptions) *GraphicOptions {
|
|||
// AddPicture provides the method to add picture in a sheet by given picture
|
||||
// format set (such as offset, scale, aspect ratio setting and print settings)
|
||||
// and file path, supported image types: BMP, EMF, EMZ, GIF, JPEG, JPG, PNG,
|
||||
// SVG, TIF, TIFF, WMF, and WMZ. This function is concurrency safe. For example:
|
||||
// SVG, TIF, TIFF, WMF, and WMZ. This function is concurrency-safe. Note that
|
||||
// this function only supports adding pictures placed over the cells currently,
|
||||
// and doesn't support adding pictures placed in cells or creating the Kingsoft
|
||||
// WPS Office embedded image cells. For example:
|
||||
//
|
||||
// package main
|
||||
//
|
||||
|
@ -125,6 +140,10 @@ func parseGraphicOptions(opts *GraphicOptions) *GraphicOptions {
|
|||
// The optional parameter "AutoFit" specifies if you make graph object size
|
||||
// auto-fits the cell, the default value of that is 'false'.
|
||||
//
|
||||
// The optional parameter "AutoFitIgnoreAspect" specifies if fill the cell with
|
||||
// the image and ignore its aspect ratio, the default value of that is 'false'.
|
||||
// This option only works when the "AutoFit" is enabled.
|
||||
//
|
||||
// The optional parameter "OffsetX" specifies the horizontal offset of the graph
|
||||
// object with the cell, the default value of that is 0.
|
||||
//
|
||||
|
@ -167,8 +186,10 @@ func (f *File) AddPicture(sheet, cell, name string, opts *GraphicOptions) error
|
|||
// AddPictureFromBytes provides the method to add picture in a sheet by given
|
||||
// picture format set (such as offset, scale, aspect ratio setting and print
|
||||
// settings), file base name, extension name and file bytes, supported image
|
||||
// types: EMF, EMZ, GIF, JPEG, JPG, PNG, SVG, TIF, TIFF, WMF, and WMZ. For
|
||||
// example:
|
||||
// types: EMF, EMZ, GIF, JPEG, JPG, PNG, SVG, TIF, TIFF, WMF, and WMZ. Note that
|
||||
// this function only supports adding pictures placed over the cells currently,
|
||||
// and doesn't support adding pictures placed in cells or creating the Kingsoft
|
||||
// WPS Office embedded image cells. For example:
|
||||
//
|
||||
// package main
|
||||
//
|
||||
|
@ -211,6 +232,9 @@ func (f *File) AddPictureFromBytes(sheet, cell string, pic *Picture) error {
|
|||
if !ok {
|
||||
return ErrImgExt
|
||||
}
|
||||
if pic.InsertType != PictureInsertTypePlaceOverCells {
|
||||
return ErrParameterInvalid
|
||||
}
|
||||
options := parseGraphicOptions(pic.Format)
|
||||
img, _, err := image.DecodeConfig(bytes.NewReader(pic.File))
|
||||
if err != nil {
|
||||
|
@ -271,6 +295,16 @@ func (f *File) addSheetLegacyDrawing(sheet string, rID int) {
|
|||
}
|
||||
}
|
||||
|
||||
// addSheetLegacyDrawingHF provides a function to add legacy drawing
|
||||
// header/footer element to xl/worksheets/sheet%d.xml by given
|
||||
// worksheet name and relationship index.
|
||||
func (f *File) addSheetLegacyDrawingHF(sheet string, rID int) {
|
||||
ws, _ := f.workSheetReader(sheet)
|
||||
ws.LegacyDrawingHF = &xlsxLegacyDrawingHF{
|
||||
RID: "rId" + strconv.Itoa(rID),
|
||||
}
|
||||
}
|
||||
|
||||
// addSheetDrawing provides a function to add drawing element to
|
||||
// xl/worksheets/sheet%d.xml by given worksheet name and relationship index.
|
||||
func (f *File) addSheetDrawing(sheet string, rID int) {
|
||||
|
@ -473,8 +507,7 @@ func (f *File) GetPictures(sheet, cell string) ([]Picture, error) {
|
|||
target := f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID)
|
||||
drawingXML := strings.TrimPrefix(strings.ReplaceAll(target, "..", "xl"), "/")
|
||||
drawingRelationships := strings.ReplaceAll(
|
||||
strings.ReplaceAll(target, "../drawings", "xl/drawings/_rels"), ".xml", ".xml.rels")
|
||||
|
||||
strings.ReplaceAll(drawingXML, "xl/drawings", "xl/drawings/_rels"), ".xml", ".xml.rels")
|
||||
imgs, err := f.getCellImages(sheet, cell)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -497,13 +530,14 @@ func (f *File) GetPictureCells(sheet string) ([]string, error) {
|
|||
}
|
||||
f.mu.Unlock()
|
||||
if ws.Drawing == nil {
|
||||
return f.getEmbeddedImageCells(sheet)
|
||||
return f.getImageCells(sheet)
|
||||
}
|
||||
target := f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID)
|
||||
drawingXML := strings.TrimPrefix(strings.ReplaceAll(target, "..", "xl"), "/")
|
||||
drawingRelationships := strings.ReplaceAll(
|
||||
strings.ReplaceAll(target, "../drawings", "xl/drawings/_rels"), ".xml", ".xml.rels")
|
||||
embeddedImageCells, err := f.getEmbeddedImageCells(sheet)
|
||||
strings.ReplaceAll(drawingXML, "xl/drawings", "xl/drawings/_rels"), ".xml", ".xml.rels")
|
||||
|
||||
embeddedImageCells, err := f.getImageCells(sheet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -577,16 +611,23 @@ func (f *File) getPicture(row, col int, drawingXML, drawingRelationships string)
|
|||
cond := func(from *xlsxFrom) bool { return from.Col == col && from.Row == row }
|
||||
cond2 := func(from *decodeFrom) bool { return from.Col == col && from.Row == row }
|
||||
cb := func(a *xdrCellAnchor, r *xlsxRelationship) {
|
||||
pic := Picture{Extension: filepath.Ext(r.Target), Format: &GraphicOptions{}}
|
||||
if buffer, _ := f.Pkg.Load(strings.ReplaceAll(r.Target, "..", "xl")); buffer != nil {
|
||||
pic := Picture{Extension: filepath.Ext(r.Target), Format: &GraphicOptions{}, InsertType: PictureInsertTypePlaceOverCells}
|
||||
if buffer, _ := f.Pkg.Load(filepath.ToSlash(filepath.Clean("xl/drawings/" + r.Target))); buffer != nil {
|
||||
pic.File = buffer.([]byte)
|
||||
pic.Format.AltText = a.Pic.NvPicPr.CNvPr.Descr
|
||||
pics = append(pics, pic)
|
||||
}
|
||||
}
|
||||
cb2 := func(a *decodeCellAnchor, r *xlsxRelationship) {
|
||||
pic := Picture{Extension: filepath.Ext(r.Target), Format: &GraphicOptions{}}
|
||||
if buffer, _ := f.Pkg.Load(strings.ReplaceAll(r.Target, "..", "xl")); buffer != nil {
|
||||
var target string
|
||||
if strings.HasPrefix(r.Target, "/") {
|
||||
target = strings.TrimPrefix(r.Target, "/")
|
||||
} else {
|
||||
target = filepath.ToSlash(filepath.Clean("xl/drawings/" + r.Target))
|
||||
}
|
||||
|
||||
pic := Picture{Extension: filepath.Ext(target), Format: &GraphicOptions{}, InsertType: PictureInsertTypePlaceOverCells}
|
||||
if buffer, _ := f.Pkg.Load(target); buffer != nil {
|
||||
pic.File = buffer.([]byte)
|
||||
pic.Format.AltText = a.Pic.NvPicPr.CNvPr.Descr
|
||||
pics = append(pics, pic)
|
||||
|
@ -715,6 +756,9 @@ func (f *File) drawingResize(sheet, cell string, width, height float64, opts *Gr
|
|||
asp := float64(cellHeight) / height
|
||||
height, width = float64(cellHeight), width*asp
|
||||
}
|
||||
if opts.AutoFitIgnoreAspect {
|
||||
width, height = float64(cellWidth), float64(cellHeight)
|
||||
}
|
||||
width, height = width-float64(opts.OffsetX), height-float64(opts.OffsetY)
|
||||
w, h = int(width*opts.ScaleX), int(height*opts.ScaleY)
|
||||
return
|
||||
|
@ -736,14 +780,21 @@ func (f *File) getPictureCells(drawingXML, drawingRelationships string) ([]strin
|
|||
cond := func(from *xlsxFrom) bool { return true }
|
||||
cond2 := func(from *decodeFrom) bool { return true }
|
||||
cb := func(a *xdrCellAnchor, r *xlsxRelationship) {
|
||||
if _, ok := f.Pkg.Load(strings.ReplaceAll(r.Target, "..", "xl")); ok {
|
||||
if _, ok := f.Pkg.Load(filepath.ToSlash(filepath.Clean("xl/drawings/" + r.Target))); ok {
|
||||
if cell, err := CoordinatesToCellName(a.From.Col+1, a.From.Row+1); err == nil && inStrSlice(cells, cell, true) == -1 {
|
||||
cells = append(cells, cell)
|
||||
}
|
||||
}
|
||||
}
|
||||
cb2 := func(a *decodeCellAnchor, r *xlsxRelationship) {
|
||||
if _, ok := f.Pkg.Load(strings.ReplaceAll(r.Target, "..", "xl")); ok {
|
||||
var target string
|
||||
if strings.HasPrefix(r.Target, "/") {
|
||||
target = strings.TrimPrefix(r.Target, "/")
|
||||
} else {
|
||||
target = filepath.ToSlash(filepath.Clean("xl/drawings/" + r.Target))
|
||||
}
|
||||
|
||||
if _, ok := f.Pkg.Load(target); ok {
|
||||
if cell, err := CoordinatesToCellName(a.From.Col+1, a.From.Row+1); err == nil && inStrSlice(cells, cell, true) == -1 {
|
||||
cells = append(cells, cell)
|
||||
}
|
||||
|
@ -771,9 +822,9 @@ func (f *File) cellImagesReader() (*decodeCellImages, error) {
|
|||
return f.DecodeCellImages, nil
|
||||
}
|
||||
|
||||
// getEmbeddedImageCells returns all the Kingsoft WPS Office embedded image
|
||||
// cells reference by given worksheet name.
|
||||
func (f *File) getEmbeddedImageCells(sheet string) ([]string, error) {
|
||||
// getImageCells returns all the cell images and the Kingsoft WPS
|
||||
// Office embedded image cells reference by given worksheet name.
|
||||
func (f *File) getImageCells(sheet string) ([]string, error) {
|
||||
var (
|
||||
err error
|
||||
cells []string
|
||||
|
@ -791,14 +842,125 @@ func (f *File) getEmbeddedImageCells(sheet string) ([]string, error) {
|
|||
}
|
||||
cells = append(cells, c.R)
|
||||
}
|
||||
r, err := f.getImageCellRel(&c, &Picture{})
|
||||
if err != nil {
|
||||
return cells, err
|
||||
}
|
||||
if r != nil {
|
||||
cells = append(cells, c.R)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return cells, err
|
||||
}
|
||||
|
||||
// getCellImages provides a function to get the Kingsoft WPS Office embedded
|
||||
// cell images by given worksheet name and cell reference.
|
||||
// getRichDataRichValueRel returns relationship of the cell image by given meta
|
||||
// blocks value.
|
||||
func (f *File) getRichDataRichValueRel(val string) (*xlsxRelationship, error) {
|
||||
var r *xlsxRelationship
|
||||
idx, err := strconv.Atoi(val)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
richValueRel, err := f.richValueRelReader()
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
if idx >= len(richValueRel.Rels) {
|
||||
return r, err
|
||||
}
|
||||
rID := richValueRel.Rels[idx].ID
|
||||
if r = f.getRichDataRichValueRelRelationships(rID); r != nil && r.Type != SourceRelationshipImage {
|
||||
return nil, err
|
||||
}
|
||||
return r, err
|
||||
}
|
||||
|
||||
// getRichDataWebImagesRel returns relationship of a web image by given meta
|
||||
// blocks value.
|
||||
func (f *File) getRichDataWebImagesRel(val string) (*xlsxRelationship, error) {
|
||||
var r *xlsxRelationship
|
||||
idx, err := strconv.Atoi(val)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
richValueWebImages, err := f.richValueWebImageReader()
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
if idx >= len(richValueWebImages.WebImageSrd) {
|
||||
return r, err
|
||||
}
|
||||
rID := richValueWebImages.WebImageSrd[idx].Blip.RID
|
||||
if r = f.getRichValueWebImageRelationships(rID); r != nil && r.Type != SourceRelationshipImage {
|
||||
return nil, err
|
||||
}
|
||||
return r, err
|
||||
}
|
||||
|
||||
// getImageCellRel returns the cell image relationship.
|
||||
func (f *File) getImageCellRel(c *xlsxC, pic *Picture) (*xlsxRelationship, error) {
|
||||
var r *xlsxRelationship
|
||||
if c.Vm == nil || c.V != formulaErrorVALUE {
|
||||
return r, nil
|
||||
}
|
||||
metaData, err := f.metadataReader()
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
vmd := metaData.ValueMetadata
|
||||
if vmd == nil || int(*c.Vm) > len(vmd.Bk) || len(vmd.Bk[*c.Vm-1].Rc) == 0 {
|
||||
return r, err
|
||||
}
|
||||
richValueIdx := vmd.Bk[*c.Vm-1].Rc[0].V
|
||||
richValue, err := f.richValueReader()
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
if richValueIdx >= len(richValue.Rv) {
|
||||
return r, err
|
||||
}
|
||||
rv := richValue.Rv[richValueIdx].V
|
||||
if len(rv) == 2 && rv[1] == "5" {
|
||||
pic.InsertType = PictureInsertTypePlaceInCell
|
||||
return f.getRichDataRichValueRel(rv[0])
|
||||
}
|
||||
// cell image inserted by IMAGE formula function
|
||||
if len(rv) > 3 && rv[1]+rv[2] == "10" {
|
||||
pic.InsertType = PictureInsertTypeIMAGE
|
||||
return f.getRichDataWebImagesRel(rv[0])
|
||||
}
|
||||
return r, err
|
||||
}
|
||||
|
||||
// getCellImages provides a function to get the cell images and
|
||||
// the Kingsoft WPS Office embedded cell images by given worksheet name and cell
|
||||
// reference.
|
||||
func (f *File) getCellImages(sheet, cell string) ([]Picture, error) {
|
||||
pics, err := f.getDispImages(sheet, cell)
|
||||
if err != nil {
|
||||
return pics, err
|
||||
}
|
||||
_, err = f.getCellStringFunc(sheet, cell, func(x *xlsxWorksheet, c *xlsxC) (string, bool, error) {
|
||||
pic := Picture{Format: &GraphicOptions{}, InsertType: PictureInsertTypePlaceInCell}
|
||||
r, err := f.getImageCellRel(c, &pic)
|
||||
if err != nil || r == nil {
|
||||
return "", true, err
|
||||
}
|
||||
pic.Extension = filepath.Ext(r.Target)
|
||||
if buffer, _ := f.Pkg.Load(strings.TrimPrefix(strings.ReplaceAll(r.Target, "..", "xl"), "/")); buffer != nil {
|
||||
pic.File = buffer.([]byte)
|
||||
pics = append(pics, pic)
|
||||
}
|
||||
return "", true, nil
|
||||
})
|
||||
return pics, err
|
||||
}
|
||||
|
||||
// getDispImages provides a function to get the Kingsoft WPS Office embedded
|
||||
// cell images by given worksheet name and cell reference.
|
||||
func (f *File) getDispImages(sheet, cell string) ([]Picture, error) {
|
||||
formula, err := f.GetCellFormula(sheet, cell)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -823,7 +985,7 @@ func (f *File) getCellImages(sheet, cell string) ([]Picture, error) {
|
|||
if cellImg.Pic.NvPicPr.CNvPr.Name == imgID {
|
||||
for _, r := range rels.Relationships {
|
||||
if r.ID == cellImg.Pic.BlipFill.Blip.Embed {
|
||||
pic := Picture{Extension: filepath.Ext(r.Target), Format: &GraphicOptions{}}
|
||||
pic := Picture{Extension: filepath.Ext(r.Target), Format: &GraphicOptions{}, InsertType: PictureInsertTypeDISPIMG}
|
||||
if buffer, _ := f.Pkg.Load("xl/" + r.Target); buffer != nil {
|
||||
pic.File = buffer.([]byte)
|
||||
pic.Format.AltText = cellImg.Pic.NvPicPr.CNvPr.Descr
|
||||
|
|
152
picture_test.go
152
picture_test.go
|
@ -12,10 +12,9 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
_ "golang.org/x/image/bmp"
|
||||
_ "golang.org/x/image/tiff"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func BenchmarkAddPictureFromBytes(b *testing.B) {
|
||||
|
@ -49,6 +48,7 @@ func TestAddPicture(t *testing.T) {
|
|||
// Test add picture to worksheet with autofit
|
||||
assert.NoError(t, f.AddPicture("Sheet1", "A30", filepath.Join("test", "images", "excel.jpg"), &GraphicOptions{AutoFit: true}))
|
||||
assert.NoError(t, f.AddPicture("Sheet1", "B30", filepath.Join("test", "images", "excel.jpg"), &GraphicOptions{OffsetX: 10, OffsetY: 10, AutoFit: true}))
|
||||
assert.NoError(t, f.AddPicture("Sheet1", "C30", filepath.Join("test", "images", "excel.jpg"), &GraphicOptions{AutoFit: true, AutoFitIgnoreAspect: true}))
|
||||
_, err = f.NewSheet("AddPicture")
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, f.SetRowHeight("AddPicture", 10, 30))
|
||||
|
@ -59,6 +59,8 @@ func TestAddPicture(t *testing.T) {
|
|||
|
||||
// Test add picture to worksheet from bytes
|
||||
assert.NoError(t, f.AddPictureFromBytes("Sheet1", "Q1", &Picture{Extension: ".png", File: file, Format: &GraphicOptions{AltText: "Excel Logo"}}))
|
||||
// Test add picture to worksheet from bytes with unsupported insert type
|
||||
assert.Equal(t, ErrParameterInvalid, f.AddPictureFromBytes("Sheet1", "Q1", &Picture{Extension: ".png", File: file, Format: &GraphicOptions{AltText: "Excel Logo"}, InsertType: PictureInsertTypePlaceInCell}))
|
||||
// Test add picture to worksheet from bytes with illegal cell reference
|
||||
assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), f.AddPictureFromBytes("Sheet1", "A", &Picture{Extension: ".png", File: file, Format: &GraphicOptions{AltText: "Excel Logo"}}))
|
||||
|
||||
|
@ -81,7 +83,7 @@ func TestAddPicture(t *testing.T) {
|
|||
// Test get picture cells
|
||||
cells, err := f.GetPictureCells("Sheet1")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []string{"F21", "A30", "B30", "Q1", "Q8", "Q15", "Q22", "Q28"}, cells)
|
||||
assert.Equal(t, []string{"F21", "A30", "B30", "C30", "Q1", "Q8", "Q15", "Q22", "Q28"}, cells)
|
||||
assert.NoError(t, f.Close())
|
||||
|
||||
f, err = OpenFile(filepath.Join("test", "TestAddPicture1.xlsx"))
|
||||
|
@ -90,7 +92,7 @@ func TestAddPicture(t *testing.T) {
|
|||
f.Drawings.Delete(path)
|
||||
cells, err = f.GetPictureCells("Sheet1")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []string{"F21", "A30", "B30", "Q1", "Q8", "Q15", "Q22", "Q28"}, cells)
|
||||
assert.Equal(t, []string{"F21", "A30", "B30", "C30", "Q1", "Q8", "Q15", "Q22", "Q28"}, cells)
|
||||
// Test get picture cells with unsupported charset
|
||||
f.Drawings.Delete(path)
|
||||
f.Pkg.Store(path, MacintoshCyrillicCharset)
|
||||
|
@ -150,6 +152,7 @@ func TestGetPicture(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.Len(t, pics[0].File, 13233)
|
||||
assert.Empty(t, pics[0].Format.AltText)
|
||||
assert.Equal(t, PictureInsertTypePlaceOverCells, pics[0].InsertType)
|
||||
|
||||
f, err = prepareTestBook1()
|
||||
if !assert.NoError(t, err) {
|
||||
|
@ -216,6 +219,19 @@ func TestGetPicture(t *testing.T) {
|
|||
cells, err := f.GetPictureCells("Sheet2")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []string{"K16"}, cells)
|
||||
|
||||
// Try to get picture cells with absolute target path in the drawing relationship
|
||||
rels, err := f.relsReader("xl/drawings/_rels/drawing2.xml.rels")
|
||||
assert.NoError(t, err)
|
||||
rels.Relationships[0].Target = "/xl/media/image2.jpeg"
|
||||
cells, err = f.GetPictureCells("Sheet2")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []string{"K16"}, cells)
|
||||
// Try to get pictures with absolute target path in the drawing relationship
|
||||
pics, err = f.GetPictures("Sheet2", "K16")
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, pics, 1)
|
||||
|
||||
assert.NoError(t, f.Close())
|
||||
|
||||
// Test get picture from none drawing worksheet
|
||||
|
@ -244,11 +260,12 @@ func TestGetPicture(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.NoError(t, f.SetCellFormula("Sheet1", "F21", "=_xlfn.DISPIMG(\"ID_********************************\",1)"))
|
||||
f.Pkg.Store(defaultXMLPathCellImages, []byte(`<etc:cellImages xmlns:etc="http://www.wps.cn/officeDocument/2017/etCustomData"><etc:cellImage><xdr:pic><xdr:nvPicPr><xdr:cNvPr id="1" name="ID_********************************" descr="CellImage1"/></xdr:nvPicPr><xdr:blipFill><a:blip r:embed="rId1"/></xdr:blipFill></xdr:pic></etc:cellImage></etc:cellImages>`))
|
||||
f.Pkg.Store(defaultXMLPathCellImagesRels, []byte(`<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/image1.jpeg"/></Relationships>`))
|
||||
f.Pkg.Store(defaultXMLPathCellImagesRels, []byte(fmt.Sprintf(`<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="%s" Target="media/image1.jpeg"/></Relationships>`, SourceRelationshipImage)))
|
||||
pics, err = f.GetPictures("Sheet1", "F21")
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, pics, 2)
|
||||
assert.Equal(t, "CellImage1", pics[0].Format.AltText)
|
||||
assert.Equal(t, PictureInsertTypeDISPIMG, pics[0].InsertType)
|
||||
|
||||
// Test get embedded cell pictures with invalid formula
|
||||
assert.NoError(t, f.SetCellFormula("Sheet1", "A1", "=_xlfn.DISPIMG()"))
|
||||
|
@ -448,13 +465,134 @@ func TestGetCellImages(t *testing.T) {
|
|||
_, err := f.getCellImages("Sheet1", "A1")
|
||||
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
|
||||
assert.NoError(t, f.Close())
|
||||
|
||||
// Test get the cell images
|
||||
prepareWorkbook := func() *File {
|
||||
f := NewFile()
|
||||
assert.NoError(t, f.AddPicture("Sheet1", "A1", filepath.Join("test", "images", "excel.png"), nil))
|
||||
f.Pkg.Store(defaultXMLMetadata, []byte(`<metadata><valueMetadata count="1"><bk><rc t="1" v="0"/></bk></valueMetadata></metadata>`))
|
||||
f.Pkg.Store(defaultXMLRdRichValuePart, []byte(`<rvData count="1"><rv s="0"><v>0</v><v>5</v></rv></rvData>`))
|
||||
f.Pkg.Store(defaultXMLRdRichValueRel, []byte(`<richValueRels><rel r:id="rId1"/></richValueRels>`))
|
||||
f.Pkg.Store(defaultXMLRdRichValueRelRels, []byte(fmt.Sprintf(`<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="%s" Target="../media/image1.png"/></Relationships>`, SourceRelationshipImage)))
|
||||
f.Sheet.Store("xl/worksheets/sheet1.xml", &xlsxWorksheet{
|
||||
SheetData: xlsxSheetData{Row: []xlsxRow{
|
||||
{R: 1, C: []xlsxC{{R: "A1", T: "e", V: formulaErrorVALUE, Vm: uintPtr(1)}}},
|
||||
}},
|
||||
})
|
||||
return f
|
||||
}
|
||||
f = prepareWorkbook()
|
||||
pics, err := f.GetPictures("Sheet1", "A1")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(pics))
|
||||
assert.Equal(t, PictureInsertTypePlaceInCell, pics[0].InsertType)
|
||||
cells, err := f.GetPictureCells("Sheet1")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []string{"A1"}, cells)
|
||||
|
||||
// Test get the cell images without image relationships parts
|
||||
f.Relationships.Delete(defaultXMLRdRichValueRelRels)
|
||||
f.Pkg.Store(defaultXMLRdRichValueRelRels, []byte(fmt.Sprintf(`<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="%s" Target="../media/image1.png"/></Relationships>`, SourceRelationshipHyperLink)))
|
||||
pics, err = f.GetPictures("Sheet1", "A1")
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, pics)
|
||||
// Test get the cell images with unsupported charset rich data rich value relationships
|
||||
f.Relationships.Delete(defaultXMLRdRichValueRelRels)
|
||||
f.Pkg.Store(defaultXMLRdRichValueRelRels, MacintoshCyrillicCharset)
|
||||
pics, err = f.GetPictures("Sheet1", "A1")
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, pics)
|
||||
// Test get the cell images with unsupported charset rich data rich value
|
||||
f.Pkg.Store(defaultXMLRdRichValueRel, MacintoshCyrillicCharset)
|
||||
_, err = f.GetPictures("Sheet1", "A1")
|
||||
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
|
||||
// Test get the image cells without block of metadata records
|
||||
cells, err = f.GetPictureCells("Sheet1")
|
||||
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
|
||||
assert.Empty(t, cells)
|
||||
// Test get the cell images with rich data rich value relationships
|
||||
f.Pkg.Store(defaultXMLMetadata, []byte(`<metadata><valueMetadata count="1"><bk><rc t="1" v="0"/></bk></valueMetadata></metadata>`))
|
||||
f.Pkg.Store(defaultXMLRdRichValueRel, []byte(`<richValueRels/>`))
|
||||
pics, err = f.GetPictures("Sheet1", "A1")
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, pics)
|
||||
// Test get the cell images with unsupported charset meta data
|
||||
f.Pkg.Store(defaultXMLMetadata, MacintoshCyrillicCharset)
|
||||
_, err = f.GetPictures("Sheet1", "A1")
|
||||
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
|
||||
// Test get the cell images without block of metadata records
|
||||
f.Pkg.Store(defaultXMLMetadata, []byte(`<metadata><valueMetadata/></metadata>`))
|
||||
pics, err = f.GetPictures("Sheet1", "A1")
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, pics)
|
||||
|
||||
f = prepareWorkbook()
|
||||
// Test get the cell images with empty image cell rich value
|
||||
f.Pkg.Store(defaultXMLRdRichValuePart, []byte(`<rvData count="1"><rv s="0"><v></v><v>5</v></rv></rvData>`))
|
||||
pics, err = f.GetPictures("Sheet1", "A1")
|
||||
assert.EqualError(t, err, "strconv.Atoi: parsing \"\": invalid syntax")
|
||||
assert.Empty(t, pics)
|
||||
// Test get the cell images without image cell rich value
|
||||
f.Pkg.Store(defaultXMLRdRichValuePart, []byte(`<rvData count="1"><rv s="0"><v>0</v><v>1</v></rv></rvData>`))
|
||||
pics, err = f.GetPictures("Sheet1", "A1")
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, pics)
|
||||
// Test get the cell images with unsupported charset rich value
|
||||
f.Pkg.Store(defaultXMLRdRichValuePart, MacintoshCyrillicCharset)
|
||||
_, err = f.GetPictures("Sheet1", "A1")
|
||||
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
|
||||
|
||||
f = prepareWorkbook()
|
||||
// Test get the cell images with invalid rich value index
|
||||
f.Pkg.Store(defaultXMLMetadata, []byte(`<metadata><valueMetadata count="1"><bk><rc t="1" v="1"/></bk></valueMetadata></metadata>`))
|
||||
pics, err = f.GetPictures("Sheet1", "A1")
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, pics)
|
||||
|
||||
f = prepareWorkbook()
|
||||
// Test get the cell images inserted by IMAGE formula function
|
||||
f.Pkg.Store(defaultXMLRdRichValuePart, []byte(`<rvData count="1"><rv s="1"><v>0</v><v>1</v><v>0</v><v>0</v></rv></rvData>`))
|
||||
f.Pkg.Store(defaultXMLRdRichValueWebImagePart, []byte(`<webImagesSrd xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><webImageSrd><address r:id="rId1"/><blip r:id="rId2"/></webImageSrd>
|
||||
</webImagesSrd>`))
|
||||
f.Pkg.Store(defaultXMLRdRichValueWebImagePartRels, []byte(fmt.Sprintf(`<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="%s" Target="https://github.com/xuri/excelize" TargetMode="External"/><Relationship Id="rId2" Type="%s" Target="../media/image1.png"/></Relationships>`, SourceRelationshipHyperLink, SourceRelationshipImage)))
|
||||
pics, err = f.GetPictures("Sheet1", "A1")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(pics))
|
||||
assert.Equal(t, PictureInsertTypeIMAGE, pics[0].InsertType)
|
||||
|
||||
// Test get the cell images inserted by IMAGE formula function with unsupported charset web images relationships
|
||||
f.Relationships.Delete(defaultXMLRdRichValueWebImagePartRels)
|
||||
f.Pkg.Store(defaultXMLRdRichValueWebImagePartRels, MacintoshCyrillicCharset)
|
||||
pics, err = f.GetPictures("Sheet1", "A1")
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, pics)
|
||||
|
||||
// Test get the cell images inserted by IMAGE formula function without image part
|
||||
f.Relationships.Delete(defaultXMLRdRichValueWebImagePartRels)
|
||||
f.Pkg.Store(defaultXMLRdRichValueWebImagePartRels, []byte(fmt.Sprintf(`<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="%s" Target="https://github.com/xuri/excelize" TargetMode="External"/><Relationship Id="rId2" Type="%s" Target="../media/image1.png"/></Relationships>`, SourceRelationshipHyperLink, SourceRelationshipHyperLink)))
|
||||
pics, err = f.GetPictures("Sheet1", "A1")
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, pics)
|
||||
// Test get the cell images inserted by IMAGE formula function with unsupported charset web images part
|
||||
f.Pkg.Store(defaultXMLRdRichValueWebImagePart, MacintoshCyrillicCharset)
|
||||
_, err = f.GetPictures("Sheet1", "A1")
|
||||
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
|
||||
// Test get the cell images inserted by IMAGE formula function with empty charset web images part
|
||||
f.Pkg.Store(defaultXMLRdRichValueWebImagePart, []byte(`<webImagesSrd xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" />`))
|
||||
pics, err = f.GetPictures("Sheet1", "A1")
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, pics)
|
||||
// Test get the cell images inserted by IMAGE formula function with invalid rich value index
|
||||
f.Pkg.Store(defaultXMLRdRichValuePart, []byte(`<rvData count="1"><rv s="1"><v></v><v>1</v><v>0</v><v>0</v></rv></rvData>`))
|
||||
_, err = f.GetPictures("Sheet1", "A1")
|
||||
assert.EqualError(t, err, "strconv.Atoi: parsing \"\": invalid syntax")
|
||||
}
|
||||
|
||||
func TestGetEmbeddedImageCells(t *testing.T) {
|
||||
func TestGetImageCells(t *testing.T) {
|
||||
f := NewFile()
|
||||
f.Sheet.Delete("xl/worksheets/sheet1.xml")
|
||||
f.Pkg.Store("xl/worksheets/sheet1.xml", MacintoshCyrillicCharset)
|
||||
_, err := f.getEmbeddedImageCells("Sheet1")
|
||||
_, err := f.getImageCells("Sheet1")
|
||||
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
|
||||
assert.NoError(t, f.Close())
|
||||
}
|
||||
|
|
144
pivotTable.go
144
pivotTable.go
|
@ -51,6 +51,7 @@ type PivotTableOptions struct {
|
|||
UseAutoFormatting bool
|
||||
PageOverThenDown bool
|
||||
MergeItem bool
|
||||
ClassicLayout bool
|
||||
CompactData bool
|
||||
ShowError bool
|
||||
ShowRowHeaders bool
|
||||
|
@ -58,10 +59,16 @@ type PivotTableOptions struct {
|
|||
ShowRowStripes bool
|
||||
ShowColStripes bool
|
||||
ShowLastColumn bool
|
||||
FieldPrintTitles bool
|
||||
ItemPrintTitles bool
|
||||
PivotTableStyleName string
|
||||
}
|
||||
|
||||
// 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,15 +85,19 @@ 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
|
||||
Name string
|
||||
Outline bool
|
||||
ShowAll bool
|
||||
InsertBlankRow bool
|
||||
Subtotal string
|
||||
DefaultSubtotal bool
|
||||
NumFmt int
|
||||
}
|
||||
|
||||
// AddPivotTable provides the method to add pivot table by given pivot table
|
||||
|
@ -210,6 +221,9 @@ func (f *File) parseFormatPivotTableSet(opts *PivotTableOptions) (*xlsxWorksheet
|
|||
if !ok {
|
||||
return dataSheet, pivotTableSheetPath, ErrSheetNotExist{pivotTableSheetName}
|
||||
}
|
||||
if opts.CompactData && opts.ClassicLayout {
|
||||
return nil, "", ErrPivotTableClassicLayout
|
||||
}
|
||||
return dataSheet, pivotTableSheetPath, err
|
||||
}
|
||||
|
||||
|
@ -260,6 +274,9 @@ func (f *File) getTableFieldsOrder(opts *PivotTableOptions) ([]string, error) {
|
|||
if err != nil {
|
||||
return order, err
|
||||
}
|
||||
if name == "" {
|
||||
return order, ErrParameterInvalid
|
||||
}
|
||||
order = append(order, name)
|
||||
}
|
||||
return order, nil
|
||||
|
@ -272,8 +289,10 @@ func (f *File) addPivotCache(opts *PivotTableOptions) error {
|
|||
if err != nil {
|
||||
return newPivotTableDataRangeError(err.Error())
|
||||
}
|
||||
// data range has been checked
|
||||
order, _ := f.getTableFieldsOrder(opts)
|
||||
order, err := f.getTableFieldsOrder(opts)
|
||||
if err != nil {
|
||||
return newPivotTableDataRangeError(err.Error())
|
||||
}
|
||||
topLeftCell, _ := CoordinatesToCellName(coordinates[0], coordinates[1])
|
||||
bottomRightCell, _ := CoordinatesToCellName(coordinates[2], coordinates[3])
|
||||
pc := xlsxPivotCacheDefinition{
|
||||
|
@ -337,7 +356,10 @@ func (f *File) addPivotTable(cacheID, pivotTableID int, opts *PivotTableOptions)
|
|||
MergeItem: &opts.MergeItem,
|
||||
CreatedVersion: pivotTableVersion,
|
||||
CompactData: &opts.CompactData,
|
||||
GridDropZones: opts.ClassicLayout,
|
||||
ShowError: &opts.ShowError,
|
||||
FieldPrintTitles: opts.FieldPrintTitles,
|
||||
ItemPrintTitles: opts.ItemPrintTitles,
|
||||
DataCaption: "Values",
|
||||
Location: &xlsxLocation{
|
||||
Ref: topLeftCell + ":" + bottomRightCell,
|
||||
|
@ -370,6 +392,12 @@ func (f *File) addPivotTable(cacheID, pivotTableID int, opts *PivotTableOptions)
|
|||
if pt.Name == "" {
|
||||
pt.Name = fmt.Sprintf("PivotTable%d", pivotTableID)
|
||||
}
|
||||
|
||||
// set classic layout
|
||||
if opts.ClassicLayout {
|
||||
pt.Compact, pt.CompactData = boolPtr(false), boolPtr(false)
|
||||
}
|
||||
|
||||
// pivot fields
|
||||
_ = f.addPivotFields(&pt, opts)
|
||||
|
||||
|
@ -447,6 +475,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{}
|
||||
|
@ -455,6 +484,7 @@ func (f *File) addPivotDataFields(pt *xlsxPivotTableDefinition, opts *PivotTable
|
|||
Name: dataFieldsName[idx],
|
||||
Fld: dataField,
|
||||
Subtotal: dataFieldsSubtotals[idx],
|
||||
NumFmtID: dataFieldsNumFmtID[idx],
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -518,6 +548,14 @@ func (f *File) addPivotColFields(pt *xlsxPivotTableDefinition, opts *PivotTableO
|
|||
return err
|
||||
}
|
||||
|
||||
// setClassicLayout provides a method to set classic layout for pivot table by
|
||||
// setting Compact and Outline to false.
|
||||
func (fld *xlsxPivotField) setClassicLayout(classicLayout bool) {
|
||||
if classicLayout {
|
||||
fld.Compact, fld.Outline = boolPtr(false), boolPtr(false)
|
||||
}
|
||||
}
|
||||
|
||||
// addPivotFields create pivot fields based on the column order of the first
|
||||
// row in the data region by given pivot table definition and option.
|
||||
func (f *File) addPivotFields(pt *xlsxPivotTableDefinition, opts *PivotTableOptions) error {
|
||||
|
@ -535,23 +573,26 @@ func (f *File) addPivotFields(pt *xlsxPivotTableDefinition, opts *PivotTableOpti
|
|||
} else {
|
||||
items = append(items, &xlsxItem{T: "default"})
|
||||
}
|
||||
|
||||
pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, &xlsxPivotField{
|
||||
fld := &xlsxPivotField{
|
||||
Name: f.getPivotTableFieldName(name, opts.Rows),
|
||||
Axis: "axisRow",
|
||||
DataField: inPivotTableField(opts.Data, name) != -1,
|
||||
Compact: &rowOptions.Compact,
|
||||
Outline: &rowOptions.Outline,
|
||||
ShowAll: rowOptions.ShowAll,
|
||||
InsertBlankRow: rowOptions.InsertBlankRow,
|
||||
DefaultSubtotal: &rowOptions.DefaultSubtotal,
|
||||
Items: &xlsxItems{
|
||||
Count: len(items),
|
||||
Item: items,
|
||||
},
|
||||
})
|
||||
}
|
||||
fld.setClassicLayout(opts.ClassicLayout)
|
||||
pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, fld)
|
||||
continue
|
||||
}
|
||||
if inPivotTableField(opts.Filter, name) != -1 {
|
||||
pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, &xlsxPivotField{
|
||||
fld := &xlsxPivotField{
|
||||
Axis: "axisPage",
|
||||
DataField: inPivotTableField(opts.Data, name) != -1,
|
||||
Name: f.getPivotTableFieldName(name, opts.Columns),
|
||||
|
@ -561,7 +602,9 @@ func (f *File) addPivotFields(pt *xlsxPivotTableDefinition, opts *PivotTableOpti
|
|||
{T: "default"},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
fld.setClassicLayout(opts.ClassicLayout)
|
||||
pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, fld)
|
||||
continue
|
||||
}
|
||||
if inPivotTableField(opts.Columns, name) != -1 {
|
||||
|
@ -572,27 +615,35 @@ func (f *File) addPivotFields(pt *xlsxPivotTableDefinition, opts *PivotTableOpti
|
|||
} else {
|
||||
items = append(items, &xlsxItem{T: "default"})
|
||||
}
|
||||
pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, &xlsxPivotField{
|
||||
fld := &xlsxPivotField{
|
||||
Name: f.getPivotTableFieldName(name, opts.Columns),
|
||||
Axis: "axisCol",
|
||||
DataField: inPivotTableField(opts.Data, name) != -1,
|
||||
Compact: &columnOptions.Compact,
|
||||
Outline: &columnOptions.Outline,
|
||||
ShowAll: columnOptions.ShowAll,
|
||||
InsertBlankRow: columnOptions.InsertBlankRow,
|
||||
DefaultSubtotal: &columnOptions.DefaultSubtotal,
|
||||
Items: &xlsxItems{
|
||||
Count: len(items),
|
||||
Item: items,
|
||||
},
|
||||
})
|
||||
}
|
||||
fld.setClassicLayout(opts.ClassicLayout)
|
||||
pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, fld)
|
||||
continue
|
||||
}
|
||||
if inPivotTableField(opts.Data, name) != -1 {
|
||||
pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, &xlsxPivotField{
|
||||
fld := &xlsxPivotField{
|
||||
DataField: true,
|
||||
})
|
||||
}
|
||||
fld.setClassicLayout(opts.ClassicLayout)
|
||||
pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, fld)
|
||||
continue
|
||||
}
|
||||
pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, &xlsxPivotField{})
|
||||
fld := &xlsxPivotField{}
|
||||
fld.setClassicLayout(opts.ClassicLayout)
|
||||
pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, fld)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
@ -682,6 +733,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 {
|
||||
|
@ -756,12 +823,11 @@ func (f *File) getPivotTableDataRange(opts *PivotTableOptions) error {
|
|||
opts.pivotDataRange = opts.DataRange
|
||||
return nil
|
||||
}
|
||||
for _, sheetName := range f.GetSheetList() {
|
||||
tables, err := f.GetTables(sheetName)
|
||||
e := ErrSheetNotExist{sheetName}
|
||||
if err != nil && err.Error() != newNotWorksheetError(sheetName).Error() && err.Error() != e.Error() {
|
||||
return err
|
||||
}
|
||||
tbls, err := f.getTables()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for sheetName, tables := range tbls {
|
||||
for _, table := range tables {
|
||||
if table.Name == opts.DataRange {
|
||||
opts.pivotDataRange, opts.namedDataRange = fmt.Sprintf("%s!%s", sheetName, table.Range), true
|
||||
|
@ -803,12 +869,15 @@ func (f *File) getPivotTable(sheet, pivotTableXML, pivotCacheRels string) (Pivot
|
|||
return opts, err
|
||||
}
|
||||
opts = PivotTableOptions{
|
||||
pivotTableXML: pivotTableXML,
|
||||
pivotCacheXML: pivotCacheXML,
|
||||
pivotSheetName: sheet,
|
||||
DataRange: fmt.Sprintf("%s!%s", sheet, pc.CacheSource.WorksheetSource.Ref),
|
||||
PivotTableRange: fmt.Sprintf("%s!%s", sheet, pt.Location.Ref),
|
||||
Name: pt.Name,
|
||||
pivotTableXML: pivotTableXML,
|
||||
pivotCacheXML: pivotCacheXML,
|
||||
pivotSheetName: sheet,
|
||||
DataRange: fmt.Sprintf("%s!%s", pc.CacheSource.WorksheetSource.Sheet, pc.CacheSource.WorksheetSource.Ref),
|
||||
PivotTableRange: fmt.Sprintf("%s!%s", sheet, pt.Location.Ref),
|
||||
Name: pt.Name,
|
||||
ClassicLayout: pt.GridDropZones,
|
||||
FieldPrintTitles: pt.FieldPrintTitles,
|
||||
ItemPrintTitles: pt.ItemPrintTitles,
|
||||
}
|
||||
if pc.CacheSource.WorksheetSource.Name != "" {
|
||||
opts.DataRange = pc.CacheSource.WorksheetSource.Name
|
||||
|
@ -886,6 +955,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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -895,7 +965,9 @@ func (f *File) extractPivotTableFields(order []string, pt *xlsxPivotTableDefinit
|
|||
// settings by given pivot table fields.
|
||||
func extractPivotTableField(data string, fld *xlsxPivotField) PivotTableField {
|
||||
pivotTableField := PivotTableField{
|
||||
Data: data,
|
||||
Data: data,
|
||||
ShowAll: fld.ShowAll,
|
||||
InsertBlankRow: fld.InsertBlankRow,
|
||||
}
|
||||
fields := []string{"Compact", "Name", "Outline", "Subtotal", "DefaultSubtotal"}
|
||||
immutable, mutable := reflect.ValueOf(*fld), reflect.ValueOf(&pivotTableField).Elem()
|
||||
|
@ -986,8 +1058,8 @@ func (f *File) DeletePivotTable(sheet, name string) error {
|
|||
return err
|
||||
}
|
||||
pivotTableCaches := map[string]int{}
|
||||
for _, sheetName := range f.GetSheetList() {
|
||||
sheetPivotTables, _ := f.GetPivotTables(sheetName)
|
||||
pivotTables, _ := f.getPivotTables()
|
||||
for _, sheetPivotTables := range pivotTables {
|
||||
for _, sheetPivotTable := range sheetPivotTables {
|
||||
pivotTableCaches[sheetPivotTable.pivotCacheXML]++
|
||||
}
|
||||
|
@ -1008,3 +1080,17 @@ func (f *File) DeletePivotTable(sheet, name string) error {
|
|||
}
|
||||
return newNoExistTableError(name)
|
||||
}
|
||||
|
||||
// getPivotTables provides a function to get all pivot tables in a workbook.
|
||||
func (f *File) getPivotTables() (map[string][]PivotTableOptions, error) {
|
||||
pivotTables := map[string][]PivotTableOptions{}
|
||||
for _, sheetName := range f.GetSheetList() {
|
||||
pts, err := f.GetPivotTables(sheetName)
|
||||
e := ErrSheetNotExist{sheetName}
|
||||
if err != nil && err.Error() != newNotWorksheetError(sheetName).Error() && err.Error() != e.Error() {
|
||||
return pivotTables, err
|
||||
}
|
||||
pivotTables[sheetName] = append(pivotTables[sheetName], pts...)
|
||||
}
|
||||
return pivotTables, nil
|
||||
}
|
||||
|
|
|
@ -31,17 +31,20 @@ func TestPivotTable(t *testing.T) {
|
|||
DataRange: "Sheet1!A1:E31",
|
||||
PivotTableRange: "Sheet1!G2:M34",
|
||||
Name: "PivotTable1",
|
||||
Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
|
||||
Rows: []PivotTableField{{Data: "Month", ShowAll: true, DefaultSubtotal: true}, {Data: "Year"}},
|
||||
Filter: []PivotTableField{{Data: "Region"}},
|
||||
Columns: []PivotTableField{{Data: "Type", DefaultSubtotal: true}},
|
||||
Data: []PivotTableField{{Data: "Sales", Subtotal: "Sum", Name: "Summarize by Sum"}},
|
||||
Columns: []PivotTableField{{Data: "Type", ShowAll: true, InsertBlankRow: true, DefaultSubtotal: true}},
|
||||
Data: []PivotTableField{{Data: "Sales", Subtotal: "Sum", Name: "Summarize by Sum", NumFmt: 38}},
|
||||
RowGrandTotals: true,
|
||||
ColGrandTotals: true,
|
||||
ShowDrill: true,
|
||||
ClassicLayout: true,
|
||||
ShowError: true,
|
||||
ShowRowHeaders: true,
|
||||
ShowColHeaders: true,
|
||||
ShowLastColumn: true,
|
||||
ShowError: true,
|
||||
FieldPrintTitles: true,
|
||||
ItemPrintTitles: true,
|
||||
PivotTableStyleName: "PivotStyleLight16",
|
||||
}
|
||||
assert.NoError(t, f.AddPivotTable(expected))
|
||||
|
@ -131,7 +134,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,
|
||||
|
@ -139,12 +142,19 @@ func TestPivotTable(t *testing.T) {
|
|||
ShowColHeaders: true,
|
||||
ShowLastColumn: true,
|
||||
}))
|
||||
|
||||
// Test get pivot table with across worksheet data range
|
||||
pivotTables, err = f.GetPivotTables("Sheet2")
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, pivotTables, 1)
|
||||
assert.Equal(t, "Sheet1!A1:E31", pivotTables[0].DataRange)
|
||||
|
||||
assert.NoError(t, f.AddPivotTable(&PivotTableOptions{
|
||||
DataRange: "Sheet1!A1:E31",
|
||||
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,
|
||||
|
@ -164,7 +174,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,
|
||||
|
@ -256,18 +266,25 @@ func TestPivotTable(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
|
||||
// Test add pivot table with invalid sheet name
|
||||
assert.EqualError(t, f.AddPivotTable(&PivotTableOptions{
|
||||
assert.Error(t, f.AddPivotTable(&PivotTableOptions{
|
||||
DataRange: "Sheet:1!A1:E31",
|
||||
PivotTableRange: "Sheet:1!G2:M34",
|
||||
Rows: []PivotTableField{{Data: "Year"}},
|
||||
}), ErrSheetNameInvalid.Error())
|
||||
}), ErrSheetNameInvalid)
|
||||
// Test add pivot table with enable ClassicLayout and CompactData in the same time
|
||||
assert.Error(t, f.AddPivotTable(&PivotTableOptions{
|
||||
DataRange: "Sheet1!A1:E31",
|
||||
PivotTableRange: "Sheet1!G2:M34",
|
||||
CompactData: true,
|
||||
ClassicLayout: true,
|
||||
}), ErrPivotTableClassicLayout)
|
||||
// Test delete pivot table with not exists worksheet
|
||||
assert.EqualError(t, f.DeletePivotTable("SheetN", "PivotTable1"), "sheet SheetN does not exist")
|
||||
// Test delete pivot table with not exists pivot table name
|
||||
assert.EqualError(t, f.DeletePivotTable("Sheet1", "PivotTableN"), "table PivotTableN does not exist")
|
||||
// Test adjust range with invalid range
|
||||
_, _, err = f.adjustRange("")
|
||||
assert.EqualError(t, err, ErrParameterRequired.Error())
|
||||
assert.Error(t, err, ErrParameterRequired)
|
||||
// Test adjust range with incorrect range
|
||||
_, _, err = f.adjustRange("sheet1!")
|
||||
assert.EqualError(t, err, "parameter is invalid")
|
||||
|
@ -293,6 +310,7 @@ func TestPivotTable(t *testing.T) {
|
|||
assert.EqualError(t, err, `parameter 'DataRange' parsing error: parameter is required`)
|
||||
// Test add pivot table with unsupported charset content types.
|
||||
f = NewFile()
|
||||
assert.NoError(t, f.SetSheetRow("Sheet1", "A1", &[]string{"Month", "Year", "Type", "Sales", "Region"}))
|
||||
f.ContentTypes = nil
|
||||
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
|
||||
assert.EqualError(t, f.AddPivotTable(&PivotTableOptions{
|
||||
|
@ -335,6 +353,8 @@ func TestPivotTable(t *testing.T) {
|
|||
f.Pkg.Store("xl/pivotTables/pivotTable1.xml", MacintoshCyrillicCharset)
|
||||
_, err = f.GetPivotTables("Sheet1")
|
||||
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
|
||||
_, err = f.getPivotTables()
|
||||
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
|
||||
assert.NoError(t, f.Close())
|
||||
}
|
||||
|
||||
|
@ -386,6 +406,25 @@ func TestPivotTableDataRange(t *testing.T) {
|
|||
f.Relationships.Delete("xl/worksheets/_rels/sheet1.xml.rels")
|
||||
f.Pkg.Delete("xl/worksheets/_rels/sheet1.xml.rels")
|
||||
assert.EqualError(t, f.DeletePivotTable("Sheet1", "PivotTable1"), "table PivotTable1 does not exist")
|
||||
|
||||
t.Run("data_range_with_empty_column", func(t *testing.T) {
|
||||
// Test add pivot table with data range doesn't organized as a list with labeled columns
|
||||
f := NewFile()
|
||||
// Create some data in a sheet
|
||||
month := []string{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}
|
||||
types := []string{"Meat", "Dairy", "Beverages", "Produce"}
|
||||
assert.NoError(t, f.SetSheetRow("Sheet1", "A1", &[]string{"Month", "", "Type"}))
|
||||
for row := 2; row < 32; row++ {
|
||||
assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("A%d", row), month[rand.Intn(12)]))
|
||||
assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("C%d", row), types[rand.Intn(4)]))
|
||||
}
|
||||
assert.Equal(t, newPivotTableDataRangeError("parameter is invalid"), f.AddPivotTable(&PivotTableOptions{
|
||||
DataRange: "Sheet1!A1:E31",
|
||||
PivotTableRange: "Sheet1!G2:M34",
|
||||
Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}},
|
||||
Data: []PivotTableField{{Data: "Type"}},
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
func TestParseFormatPivotTableSet(t *testing.T) {
|
||||
|
@ -477,7 +516,7 @@ func TestDeleteWorkbookPivotCache(t *testing.T) {
|
|||
f := NewFile()
|
||||
// Test delete workbook pivot table cache with unsupported workbook charset
|
||||
f.WorkBook = nil
|
||||
f.Pkg.Store("xl/workbook.xml", MacintoshCyrillicCharset)
|
||||
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
|
||||
assert.EqualError(t, f.deleteWorkbookPivotCache(PivotTableOptions{pivotCacheXML: "pivotCache/pivotCacheDefinition1.xml"}), "XML syntax error on line 1: invalid UTF-8")
|
||||
|
||||
// Test delete workbook pivot table cache with unsupported workbook relationships charset
|
||||
|
|
92
rows.go
92
rows.go
|
@ -20,7 +20,7 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/mohae/deepcopy"
|
||||
"github.com/tiendc/go-deepcopy"
|
||||
)
|
||||
|
||||
// duplicateHelperFunc defines functions to duplicate helper.
|
||||
|
@ -59,10 +59,10 @@ var duplicateHelperFunc = [3]func(*File, *xlsxWorksheet, string, int, int) error
|
|||
// fmt.Println()
|
||||
// }
|
||||
func (f *File) GetRows(sheet string, opts ...Options) ([][]string, error) {
|
||||
if _, err := f.workSheetReader(sheet); err != nil {
|
||||
rows, err := f.Rows(sheet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rows, _ := f.Rows(sheet)
|
||||
results, cur, maxVal := make([][]string, 0, 64), 0, 0
|
||||
for rows.Next() {
|
||||
cur++
|
||||
|
@ -70,8 +70,11 @@ func (f *File) GetRows(sheet string, opts ...Options) ([][]string, error) {
|
|||
if err != nil {
|
||||
break
|
||||
}
|
||||
results = append(results, row)
|
||||
if len(row) > 0 {
|
||||
if emptyRows := cur - maxVal - 1; emptyRows > 0 {
|
||||
results = append(results, make([][]string, emptyRows)...)
|
||||
}
|
||||
results = append(results, row)
|
||||
maxVal = cur
|
||||
}
|
||||
}
|
||||
|
@ -392,7 +395,7 @@ func (f *File) getRowHeight(sheet string, row int) int {
|
|||
defer ws.mu.Unlock()
|
||||
for i := range ws.SheetData.Row {
|
||||
v := &ws.SheetData.Row[i]
|
||||
if v.R != nil && *v.R == row && v.Ht != nil {
|
||||
if v.R == row && v.Ht != nil {
|
||||
return int(convertRowHeightToPixels(*v.Ht))
|
||||
}
|
||||
}
|
||||
|
@ -423,7 +426,7 @@ func (f *File) GetRowHeight(sheet string, row int) (float64, error) {
|
|||
return ht, nil // it will be better to use 0, but we take care with BC
|
||||
}
|
||||
for _, v := range ws.SheetData.Row {
|
||||
if v.R != nil && *v.R == row && v.Ht != nil {
|
||||
if v.R == row && v.Ht != nil {
|
||||
return *v.Ht, nil
|
||||
}
|
||||
}
|
||||
|
@ -578,7 +581,7 @@ func (f *File) RemoveRow(sheet string, row int) error {
|
|||
keep := 0
|
||||
for rowIdx := 0; rowIdx < len(ws.SheetData.Row); rowIdx++ {
|
||||
v := &ws.SheetData.Row[rowIdx]
|
||||
if v.R != nil && *v.R != row {
|
||||
if v.R != row {
|
||||
ws.SheetData.Row[keep] = *v
|
||||
keep++
|
||||
}
|
||||
|
@ -649,8 +652,8 @@ func (f *File) DuplicateRowTo(sheet string, row, row2 int) error {
|
|||
var rowCopy xlsxRow
|
||||
|
||||
for i, r := range ws.SheetData.Row {
|
||||
if *r.R == row {
|
||||
rowCopy = deepcopy.Copy(ws.SheetData.Row[i]).(xlsxRow)
|
||||
if r.R == row {
|
||||
deepcopy.Copy(&rowCopy, ws.SheetData.Row[i])
|
||||
ok = true
|
||||
break
|
||||
}
|
||||
|
@ -666,7 +669,7 @@ func (f *File) DuplicateRowTo(sheet string, row, row2 int) error {
|
|||
|
||||
idx2 := -1
|
||||
for i, r := range ws.SheetData.Row {
|
||||
if *r.R == row2 {
|
||||
if r.R == row2 {
|
||||
idx2 = i
|
||||
break
|
||||
}
|
||||
|
@ -688,26 +691,47 @@ func (f *File) DuplicateRowTo(sheet string, row, row2 int) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// duplicateSQRefHelper provides a function to adjust conditional formatting and
|
||||
// data validations cell reference when duplicate rows.
|
||||
func duplicateSQRefHelper(row, row2 int, ref string) (string, error) {
|
||||
if !strings.Contains(ref, ":") {
|
||||
ref += ":" + ref
|
||||
}
|
||||
abs := strings.Contains(ref, "$")
|
||||
coordinates, err := rangeRefToCoordinates(ref)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3]
|
||||
if y1 == y2 && y1 == row {
|
||||
if ref, err = coordinatesToRangeRef([]int{x1, row2, x2, row2}, abs); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return ref, err
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
// duplicateConditionalFormat create conditional formatting for the destination
|
||||
// row if there are conditional formats in the copied row.
|
||||
func (f *File) duplicateConditionalFormat(ws *xlsxWorksheet, sheet string, row, row2 int) error {
|
||||
var cfs []*xlsxConditionalFormatting
|
||||
for _, cf := range ws.ConditionalFormatting {
|
||||
if cf != nil {
|
||||
if !strings.Contains(cf.SQRef, ":") {
|
||||
cf.SQRef += ":" + cf.SQRef
|
||||
}
|
||||
abs := strings.Contains(cf.SQRef, "$")
|
||||
coordinates, err := rangeRefToCoordinates(cf.SQRef)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3]
|
||||
if y1 == y2 && y1 == row {
|
||||
cfCopy := deepcopy.Copy(*cf).(xlsxConditionalFormatting)
|
||||
if cfCopy.SQRef, err = f.coordinatesToRangeRef([]int{x1, row2, x2, row2}, abs); err != nil {
|
||||
var SQRef []string
|
||||
for _, ref := range strings.Split(cf.SQRef, " ") {
|
||||
coordinates, err := duplicateSQRefHelper(row, row2, ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if coordinates != "" {
|
||||
SQRef = append(SQRef, coordinates)
|
||||
}
|
||||
}
|
||||
if len(SQRef) > 0 {
|
||||
var cfCopy xlsxConditionalFormatting
|
||||
deepcopy.Copy(&cfCopy, *cf)
|
||||
cfCopy.SQRef = strings.Join(SQRef, " ")
|
||||
cfs = append(cfs, &cfCopy)
|
||||
}
|
||||
}
|
||||
|
@ -725,20 +749,20 @@ func (f *File) duplicateDataValidations(ws *xlsxWorksheet, sheet string, row, ro
|
|||
var dvs []*xlsxDataValidation
|
||||
for _, dv := range ws.DataValidations.DataValidation {
|
||||
if dv != nil {
|
||||
if !strings.Contains(dv.Sqref, ":") {
|
||||
dv.Sqref += ":" + dv.Sqref
|
||||
}
|
||||
abs := strings.Contains(dv.Sqref, "$")
|
||||
coordinates, err := rangeRefToCoordinates(dv.Sqref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3]
|
||||
if y1 == y2 && y1 == row {
|
||||
dvCopy := deepcopy.Copy(*dv).(xlsxDataValidation)
|
||||
if dvCopy.Sqref, err = f.coordinatesToRangeRef([]int{x1, row2, x2, row2}, abs); err != nil {
|
||||
var SQRef []string
|
||||
for _, ref := range strings.Split(dv.Sqref, " ") {
|
||||
coordinates, err := duplicateSQRefHelper(row, row2, ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if coordinates != "" {
|
||||
SQRef = append(SQRef, coordinates)
|
||||
}
|
||||
}
|
||||
if len(SQRef) > 0 {
|
||||
var dvCopy xlsxDataValidation
|
||||
deepcopy.Copy(&dvCopy, *dv)
|
||||
dvCopy.Sqref = strings.Join(SQRef, " ")
|
||||
dvs = append(dvs, &dvCopy)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -892,7 +892,7 @@ func TestDuplicateRow(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
|
||||
expected := []ConditionalFormatOptions{
|
||||
{Type: "cell", Criteria: "greater than", Format: format, Value: "0"},
|
||||
{Type: "cell", Criteria: "greater than", Format: &format, Value: "0"},
|
||||
}
|
||||
assert.NoError(t, f.SetConditionalFormat("Sheet1", "A1", expected))
|
||||
|
||||
|
|
112
sheet.go
112
sheet.go
|
@ -27,7 +27,7 @@ import (
|
|||
"unicode/utf16"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/mohae/deepcopy"
|
||||
"github.com/tiendc/go-deepcopy"
|
||||
)
|
||||
|
||||
// NewSheet provides the function to create a new sheet by given a worksheet
|
||||
|
@ -124,7 +124,8 @@ func (f *File) mergeExpandedCols(ws *xlsxWorksheet) {
|
|||
Width: ws.Cols.Col[i-1].Width,
|
||||
}, ws.Cols.Col[i]); i++ {
|
||||
}
|
||||
column := deepcopy.Copy(ws.Cols.Col[left]).(xlsxCol)
|
||||
var column xlsxCol
|
||||
deepcopy.Copy(&column, ws.Cols.Col[left])
|
||||
if left < i-1 {
|
||||
column.Max = ws.Cols.Col[i-1].Min
|
||||
}
|
||||
|
@ -167,7 +168,7 @@ func (f *File) workSheetWriter() {
|
|||
_, ok := f.checked.Load(p.(string))
|
||||
if ok {
|
||||
f.Sheet.Delete(p.(string))
|
||||
f.checked.Store(p.(string), false)
|
||||
f.checked.Delete(p.(string))
|
||||
}
|
||||
buffer.Reset()
|
||||
}
|
||||
|
@ -363,7 +364,7 @@ func (f *File) SetSheetName(source, target string) error {
|
|||
if err = checkSheetName(target); err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.EqualFold(target, source) {
|
||||
if target == source {
|
||||
return err
|
||||
}
|
||||
wb, _ := f.workbookReader()
|
||||
|
@ -600,6 +601,49 @@ func (f *File) DeleteSheet(sheet string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// MoveSheet moves a sheet to a specified position in the workbook. The function
|
||||
// moves the source sheet before the target sheet. After moving, other sheets
|
||||
// will be shifted to the left or right. If the sheet is already at the target
|
||||
// position, the function will not perform any action. Not that this function
|
||||
// will be ungroup all sheets after moving. For example, move Sheet2 before
|
||||
// Sheet1:
|
||||
//
|
||||
// err := f.MoveSheet("Sheet2", "Sheet1")
|
||||
func (f *File) MoveSheet(source, target string) error {
|
||||
if strings.EqualFold(source, target) {
|
||||
return nil
|
||||
}
|
||||
wb, err := f.workbookReader()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sourceIdx, err := f.GetSheetIndex(source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
targetIdx, err := f.GetSheetIndex(target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if sourceIdx < 0 {
|
||||
return ErrSheetNotExist{source}
|
||||
}
|
||||
if targetIdx < 0 {
|
||||
return ErrSheetNotExist{target}
|
||||
}
|
||||
_ = f.UngroupSheets()
|
||||
activeSheetName := f.GetSheetName(f.GetActiveSheetIndex())
|
||||
sourceSheet := wb.Sheets.Sheet[sourceIdx]
|
||||
wb.Sheets.Sheet = append(wb.Sheets.Sheet[:sourceIdx], wb.Sheets.Sheet[sourceIdx+1:]...)
|
||||
if targetIdx > sourceIdx {
|
||||
targetIdx--
|
||||
}
|
||||
wb.Sheets.Sheet = append(wb.Sheets.Sheet[:targetIdx], append([]xlsxSheet{sourceSheet}, wb.Sheets.Sheet[targetIdx:]...)...)
|
||||
activeSheetIdx, _ := f.GetSheetIndex(activeSheetName)
|
||||
f.SetActiveSheet(activeSheetIdx)
|
||||
return err
|
||||
}
|
||||
|
||||
// deleteAndAdjustDefinedNames delete and adjust defined name in the workbook
|
||||
// by given worksheet ID.
|
||||
func deleteAndAdjustDefinedNames(wb *xlsxWorkbook, deleteLocalSheetID int) {
|
||||
|
@ -707,7 +751,8 @@ func (f *File) copySheet(from, to int) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
worksheet := deepcopy.Copy(sheet).(*xlsxWorksheet)
|
||||
worksheet := &xlsxWorksheet{}
|
||||
deepcopy.Copy(worksheet, sheet)
|
||||
toSheetID := strconv.Itoa(f.getSheetID(f.GetSheetName(to)))
|
||||
sheetXMLPath := "xl/worksheets/sheet" + toSheetID + ".xml"
|
||||
if len(worksheet.SheetViews.SheetView) > 0 {
|
||||
|
@ -773,6 +818,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
|
||||
}
|
||||
|
@ -1191,7 +1241,7 @@ func attrValToBool(name string, attrs []xml.Attr) (val bool, err error) {
|
|||
// |
|
||||
// &F | Current workbook's file name
|
||||
// |
|
||||
// &G | Drawing object as background (Not support currently)
|
||||
// &G | Drawing object as background (Use AddHeaderFooterImage)
|
||||
// |
|
||||
// &H | Shadow text format
|
||||
// |
|
||||
|
@ -1561,8 +1611,7 @@ func (f *File) SetPageLayout(sheet string, opts *PageLayoutOptions) error {
|
|||
if opts == nil {
|
||||
return err
|
||||
}
|
||||
ws.setPageSetUp(opts)
|
||||
return err
|
||||
return ws.setPageSetUp(opts)
|
||||
}
|
||||
|
||||
// newPageSetUp initialize page setup settings for the worksheet if which not
|
||||
|
@ -1574,12 +1623,15 @@ func (ws *xlsxWorksheet) newPageSetUp() {
|
|||
}
|
||||
|
||||
// setPageSetUp set page setup settings for the worksheet by given options.
|
||||
func (ws *xlsxWorksheet) setPageSetUp(opts *PageLayoutOptions) {
|
||||
func (ws *xlsxWorksheet) setPageSetUp(opts *PageLayoutOptions) error {
|
||||
if opts.Size != nil {
|
||||
ws.newPageSetUp()
|
||||
ws.PageSetUp.PaperSize = opts.Size
|
||||
}
|
||||
if opts.Orientation != nil && (*opts.Orientation == "portrait" || *opts.Orientation == "landscape") {
|
||||
if opts.Orientation != nil {
|
||||
if inStrSlice(supportedPageOrientation, *opts.Orientation, true) == -1 {
|
||||
return newInvalidPageLayoutValueError("Orientation", *opts.Orientation, strings.Join(supportedPageOrientation, ", "))
|
||||
}
|
||||
ws.newPageSetUp()
|
||||
ws.PageSetUp.Orientation = *opts.Orientation
|
||||
}
|
||||
|
@ -1588,7 +1640,10 @@ func (ws *xlsxWorksheet) setPageSetUp(opts *PageLayoutOptions) {
|
|||
ws.PageSetUp.FirstPageNumber = strconv.Itoa(int(*opts.FirstPageNumber))
|
||||
ws.PageSetUp.UseFirstPageNumber = true
|
||||
}
|
||||
if opts.AdjustTo != nil && 10 <= *opts.AdjustTo && *opts.AdjustTo <= 400 {
|
||||
if opts.AdjustTo != nil {
|
||||
if *opts.AdjustTo < 10 || 400 < *opts.AdjustTo {
|
||||
return ErrPageSetupAdjustTo
|
||||
}
|
||||
ws.newPageSetUp()
|
||||
ws.PageSetUp.Scale = int(*opts.AdjustTo)
|
||||
}
|
||||
|
@ -1604,13 +1659,21 @@ func (ws *xlsxWorksheet) setPageSetUp(opts *PageLayoutOptions) {
|
|||
ws.newPageSetUp()
|
||||
ws.PageSetUp.BlackAndWhite = *opts.BlackAndWhite
|
||||
}
|
||||
if opts.PageOrder != nil {
|
||||
if inStrSlice(supportedPageOrder, *opts.PageOrder, true) == -1 {
|
||||
return newInvalidPageLayoutValueError("PageOrder", *opts.PageOrder, strings.Join(supportedPageOrder, ", "))
|
||||
}
|
||||
ws.newPageSetUp()
|
||||
ws.PageSetUp.PageOrder = *opts.PageOrder
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPageLayout provides a function to gets worksheet page layout.
|
||||
func (f *File) GetPageLayout(sheet string) (PageLayoutOptions, error) {
|
||||
opts := PageLayoutOptions{
|
||||
Size: intPtr(0),
|
||||
Orientation: stringPtr("portrait"),
|
||||
Orientation: stringPtr(supportedPageOrientation[0]),
|
||||
FirstPageNumber: uintPtr(1),
|
||||
AdjustTo: uintPtr(100),
|
||||
}
|
||||
|
@ -1638,6 +1701,9 @@ func (f *File) GetPageLayout(sheet string) (PageLayoutOptions, error) {
|
|||
opts.FitToWidth = ws.PageSetUp.FitToWidth
|
||||
}
|
||||
opts.BlackAndWhite = boolPtr(ws.PageSetUp.BlackAndWhite)
|
||||
if ws.PageSetUp.PageOrder != "" {
|
||||
opts.PageOrder = stringPtr(ws.PageSetUp.PageOrder)
|
||||
}
|
||||
}
|
||||
return opts, err
|
||||
}
|
||||
|
@ -1652,6 +1718,24 @@ func (f *File) GetPageLayout(sheet string) (PageLayoutOptions, error) {
|
|||
// Comment: "defined name comment",
|
||||
// Scope: "Sheet2",
|
||||
// })
|
||||
//
|
||||
// If you fill the RefersTo property with only one columns range without a
|
||||
// comma, it will work as "Columns to repeat at left" only. For example:
|
||||
//
|
||||
// err := f.SetDefinedName(&excelize.DefinedName{
|
||||
// Name: "_xlnm.Print_Titles",
|
||||
// RefersTo: "Sheet1!$A:$A",
|
||||
// Scope: "Sheet1",
|
||||
// })
|
||||
//
|
||||
// If you fill the RefersTo property with only one rows range without a comma,
|
||||
// it will work as "Rows to repeat at top" only. For example:
|
||||
//
|
||||
// err := f.SetDefinedName(&excelize.DefinedName{
|
||||
// Name: "_xlnm.Print_Titles",
|
||||
// RefersTo: "Sheet1!$1:$1",
|
||||
// Scope: "Sheet1",
|
||||
// })
|
||||
func (f *File) SetDefinedName(definedName *DefinedName) error {
|
||||
if definedName.Name == "" || definedName.RefersTo == "" {
|
||||
return ErrParameterInvalid
|
||||
|
@ -1957,7 +2041,7 @@ func (ws *xlsxWorksheet) prepareSheetXML(col int, row int) {
|
|||
if rowCount < row {
|
||||
// append missing rows
|
||||
for rowIdx := rowCount; rowIdx < row; rowIdx++ {
|
||||
ws.SheetData.Row = append(ws.SheetData.Row, xlsxRow{R: intPtr(rowIdx + 1), CustomHeight: customHeight, Ht: ht, C: make([]xlsxC, 0, sizeHint)})
|
||||
ws.SheetData.Row = append(ws.SheetData.Row, xlsxRow{R: rowIdx + 1, CustomHeight: customHeight, Ht: ht, C: make([]xlsxC, 0, sizeHint)})
|
||||
}
|
||||
}
|
||||
rowData := &ws.SheetData.Row[row-1]
|
||||
|
@ -2014,7 +2098,7 @@ func (f *File) SetSheetDimension(sheet string, rangeRef string) error {
|
|||
return err
|
||||
}
|
||||
_ = sortCoordinates(coordinates)
|
||||
ref, err := f.coordinatesToRangeRef(coordinates)
|
||||
ref, err := coordinatesToRangeRef(coordinates)
|
||||
ws.Dimension = &xlsxDimension{Ref: ref}
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -210,6 +210,7 @@ func TestSetPageLayout(t *testing.T) {
|
|||
FitToHeight: intPtr(2),
|
||||
FitToWidth: intPtr(2),
|
||||
BlackAndWhite: boolPtr(true),
|
||||
PageOrder: stringPtr("overThenDown"),
|
||||
}
|
||||
assert.NoError(t, f.SetPageLayout("Sheet1", &expected))
|
||||
opts, err := f.GetPageLayout("Sheet1")
|
||||
|
@ -219,6 +220,16 @@ func TestSetPageLayout(t *testing.T) {
|
|||
assert.EqualError(t, f.SetPageLayout("SheetN", nil), "sheet SheetN does not exist")
|
||||
// Test set page layout with invalid sheet name
|
||||
assert.EqualError(t, f.SetPageLayout("Sheet:1", nil), ErrSheetNameInvalid.Error())
|
||||
// Test set page layout with invalid parameters
|
||||
assert.EqualError(t, f.SetPageLayout("Sheet1", &PageLayoutOptions{
|
||||
AdjustTo: uintPtr(5),
|
||||
}), "adjust to value must be between 10 and 400")
|
||||
assert.EqualError(t, f.SetPageLayout("Sheet1", &PageLayoutOptions{
|
||||
Orientation: stringPtr("x"),
|
||||
}), "invalid Orientation value \"x\", acceptable value should be one of portrait, landscape")
|
||||
assert.EqualError(t, f.SetPageLayout("Sheet1", &PageLayoutOptions{
|
||||
PageOrder: stringPtr("x"),
|
||||
}), "invalid PageOrder value \"x\", acceptable value should be one of overThenDown, downThenOver")
|
||||
}
|
||||
|
||||
func TestGetPageLayout(t *testing.T) {
|
||||
|
@ -465,8 +476,11 @@ func TestSetSheetName(t *testing.T) {
|
|||
// Test set worksheet with the same name
|
||||
assert.NoError(t, f.SetSheetName("Sheet1", "Sheet1"))
|
||||
assert.Equal(t, "Sheet1", f.GetSheetName(0))
|
||||
// Test set worksheet with the different name
|
||||
assert.NoError(t, f.SetSheetName("Sheet1", "sheet1"))
|
||||
assert.Equal(t, "sheet1", f.GetSheetName(0))
|
||||
// Test set sheet name with invalid sheet name
|
||||
assert.EqualError(t, f.SetSheetName("Sheet:1", "Sheet1"), ErrSheetNameInvalid.Error())
|
||||
assert.Equal(t, f.SetSheetName("Sheet:1", "Sheet1"), ErrSheetNameInvalid)
|
||||
|
||||
// Test set worksheet name with existing defined name and auto filter
|
||||
assert.NoError(t, f.AutoFilter("Sheet1", "A1:A2", nil))
|
||||
|
@ -544,6 +558,43 @@ func TestDeleteSheet(t *testing.T) {
|
|||
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDeleteSheet2.xlsx")))
|
||||
}
|
||||
|
||||
func TestMoveSheet(t *testing.T) {
|
||||
f := NewFile()
|
||||
defer f.Close()
|
||||
for i := 2; i < 6; i++ {
|
||||
_, err := f.NewSheet("Sheet" + strconv.Itoa(i))
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
assert.Equal(t, []string{"Sheet1", "Sheet2", "Sheet3", "Sheet4", "Sheet5"}, f.GetSheetList())
|
||||
|
||||
// Move target to first position
|
||||
assert.NoError(t, f.MoveSheet("Sheet2", "Sheet1"))
|
||||
assert.Equal(t, []string{"Sheet2", "Sheet1", "Sheet3", "Sheet4", "Sheet5"}, f.GetSheetList())
|
||||
assert.Equal(t, "Sheet1", f.GetSheetName(f.GetActiveSheetIndex()))
|
||||
|
||||
// Move target to last position
|
||||
assert.NoError(t, f.MoveSheet("Sheet2", "Sheet5"))
|
||||
assert.NoError(t, f.MoveSheet("Sheet5", "Sheet2"))
|
||||
assert.Equal(t, []string{"Sheet1", "Sheet3", "Sheet4", "Sheet5", "Sheet2"}, f.GetSheetList())
|
||||
|
||||
// Move target to same position
|
||||
assert.NoError(t, f.MoveSheet("Sheet1", "Sheet1"))
|
||||
assert.Equal(t, []string{"Sheet1", "Sheet3", "Sheet4", "Sheet5", "Sheet2"}, f.GetSheetList())
|
||||
|
||||
// Test move sheet with invalid sheet name
|
||||
assert.Equal(t, ErrSheetNameBlank, f.MoveSheet("", "Sheet2"))
|
||||
assert.Equal(t, ErrSheetNameBlank, f.MoveSheet("Sheet1", ""))
|
||||
|
||||
// Test move sheet on not exists worksheet
|
||||
assert.Equal(t, ErrSheetNotExist{"SheetN"}, f.MoveSheet("SheetN", "Sheet2"))
|
||||
assert.Equal(t, ErrSheetNotExist{"SheetN"}, f.MoveSheet("Sheet1", "SheetN"))
|
||||
|
||||
// Test move sheet with unsupported workbook charset
|
||||
f.WorkBook = nil
|
||||
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
|
||||
assert.EqualError(t, f.MoveSheet("Sheet2", "Sheet1"), "XML syntax error on line 1: invalid UTF-8")
|
||||
}
|
||||
|
||||
func TestDeleteAndAdjustDefinedNames(t *testing.T) {
|
||||
deleteAndAdjustDefinedNames(nil, 0)
|
||||
deleteAndAdjustDefinedNames(&xlsxWorkbook{}, 0)
|
||||
|
@ -567,6 +618,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) {
|
||||
|
|
357
slicer.go
357
slicer.go
|
@ -53,17 +53,23 @@ import (
|
|||
//
|
||||
// Format specifies the format of the slicer, this setting is optional.
|
||||
type SlicerOptions struct {
|
||||
Name string
|
||||
Cell string
|
||||
TableSheet string
|
||||
TableName string
|
||||
Caption string
|
||||
Macro string
|
||||
Width uint
|
||||
Height uint
|
||||
DisplayHeader *bool
|
||||
ItemDesc bool
|
||||
Format GraphicOptions
|
||||
slicerXML string
|
||||
slicerCacheXML string
|
||||
slicerCacheName string
|
||||
slicerSheetName string
|
||||
slicerSheetRID string
|
||||
drawingXML string
|
||||
Name string
|
||||
Cell string
|
||||
TableSheet string
|
||||
TableName string
|
||||
Caption string
|
||||
Macro string
|
||||
Width uint
|
||||
Height uint
|
||||
DisplayHeader *bool
|
||||
ItemDesc bool
|
||||
Format GraphicOptions
|
||||
}
|
||||
|
||||
// AddSlicer function inserts a slicer by giving the worksheet name and slicer
|
||||
|
@ -99,7 +105,7 @@ func (f *File) AddSlicer(sheet string, opts *SlicerOptions) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
slicerCacheName, err := f.setSlicerCache(sheet, colIdx, opts, table, pivotTable)
|
||||
slicerCacheName, err := f.setSlicerCache(colIdx, opts, table, pivotTable)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -224,7 +230,6 @@ func (f *File) addSheetSlicer(sheet, extURI string) (int, error) {
|
|||
slicerID = f.countSlicers() + 1
|
||||
ws, err = f.workSheetReader(sheet)
|
||||
decodeExtLst = new(decodeExtLst)
|
||||
slicerList = new(decodeSlicerList)
|
||||
)
|
||||
if err != nil {
|
||||
return slicerID, err
|
||||
|
@ -236,6 +241,7 @@ func (f *File) addSheetSlicer(sheet, extURI string) (int, error) {
|
|||
}
|
||||
for _, ext := range decodeExtLst.Ext {
|
||||
if ext.URI == extURI {
|
||||
slicerList := new(decodeSlicerList)
|
||||
_ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(slicerList)
|
||||
for _, slicer := range slicerList.Slicer {
|
||||
if slicer.RID != "" {
|
||||
|
@ -390,14 +396,13 @@ func (f *File) genSlicerCacheName(name string) string {
|
|||
// setSlicerCache check if a slicer cache already exists or add a new slicer
|
||||
// cache by giving the column index, slicer, table options, and returns the
|
||||
// slicer cache name.
|
||||
func (f *File) setSlicerCache(sheet string, colIdx int, opts *SlicerOptions, table *Table, pivotTable *PivotTableOptions) (string, error) {
|
||||
func (f *File) setSlicerCache(colIdx int, opts *SlicerOptions, table *Table, pivotTable *PivotTableOptions) (string, error) {
|
||||
var ok bool
|
||||
var slicerCacheName string
|
||||
f.Pkg.Range(func(k, v interface{}) bool {
|
||||
if strings.Contains(k.(string), "xl/slicerCaches/slicerCache") {
|
||||
slicerCache := &xlsxSlicerCacheDefinition{}
|
||||
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(v.([]byte)))).
|
||||
Decode(slicerCache); err != nil && err != io.EOF {
|
||||
slicerCache, err := f.slicerCacheReader(k.(string))
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if pivotTable != nil && slicerCache.PivotTables != nil {
|
||||
|
@ -449,6 +454,20 @@ func (f *File) slicerReader(slicerXML string) (*xlsxSlicers, error) {
|
|||
return slicer, nil
|
||||
}
|
||||
|
||||
// slicerCacheReader provides a function to get the pointer to the structure
|
||||
// after deserialization of xl/slicerCaches/slicerCache%d.xml.
|
||||
func (f *File) slicerCacheReader(slicerCacheXML string) (*xlsxSlicerCacheDefinition, error) {
|
||||
content, ok := f.Pkg.Load(slicerCacheXML)
|
||||
slicerCache := &xlsxSlicerCacheDefinition{}
|
||||
if ok && content != nil {
|
||||
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(content.([]byte)))).
|
||||
Decode(slicerCache); err != nil && err != io.EOF {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return slicerCache, nil
|
||||
}
|
||||
|
||||
// timelineReader provides a function to get the pointer to the structure
|
||||
// after deserialization of xl/timelines/timeline%d.xml.
|
||||
func (f *File) timelineReader(timelineXML string) (*xlsxTimelines, error) {
|
||||
|
@ -586,6 +605,7 @@ func (f *File) addDrawingSlicer(sheet, slicerName string, ns xml.Attr, opts *Sli
|
|||
return err
|
||||
}
|
||||
graphicFrame := xlsxGraphicFrame{
|
||||
Macro: opts.Macro,
|
||||
NvGraphicFramePr: xlsxNvGraphicFramePr{
|
||||
CNvPr: &xlsxCNvPr{
|
||||
ID: cNvPrID,
|
||||
|
@ -725,3 +745,306 @@ func (f *File) addWorkbookSlicerCache(slicerCacheID int, URI string) error {
|
|||
wb.ExtLst = &xlsxExtLst{Ext: strings.TrimSuffix(strings.TrimPrefix(string(extLstBytes), "<extLst>"), "</extLst>")}
|
||||
return err
|
||||
}
|
||||
|
||||
// GetSlicers provides the method to get all slicers in a worksheet by a given
|
||||
// worksheet name. Note that, this function does not support getting the height,
|
||||
// width, and graphic options of the slicer shape currently.
|
||||
func (f *File) GetSlicers(sheet string) ([]SlicerOptions, error) {
|
||||
var (
|
||||
slicers []SlicerOptions
|
||||
ws, err = f.workSheetReader(sheet)
|
||||
decodeExtLst = new(decodeExtLst)
|
||||
)
|
||||
if err != nil {
|
||||
return slicers, err
|
||||
}
|
||||
if ws.ExtLst == nil {
|
||||
return slicers, err
|
||||
}
|
||||
target := f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID)
|
||||
drawingXML := strings.TrimPrefix(strings.ReplaceAll(target, "..", "xl"), "/")
|
||||
if err = f.xmlNewDecoder(strings.NewReader("<extLst>" + ws.ExtLst.Ext + "</extLst>")).
|
||||
Decode(decodeExtLst); err != nil && err != io.EOF {
|
||||
return slicers, err
|
||||
}
|
||||
for _, ext := range decodeExtLst.Ext {
|
||||
if ext.URI == ExtURISlicerListX14 || ext.URI == ExtURISlicerListX15 {
|
||||
slicerList := new(decodeSlicerList)
|
||||
_ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(&slicerList)
|
||||
for _, slicer := range slicerList.Slicer {
|
||||
if slicer.RID != "" {
|
||||
opts, err := f.getSlicers(sheet, slicer.RID, drawingXML)
|
||||
if err != nil {
|
||||
return slicers, err
|
||||
}
|
||||
slicers = append(slicers, opts...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return slicers, err
|
||||
}
|
||||
|
||||
// getSlicerCache provides a function to get a slicer cache by given slicer
|
||||
// cache name and slicer options.
|
||||
func (f *File) getSlicerCache(slicerCacheName string, opt *SlicerOptions) *xlsxSlicerCacheDefinition {
|
||||
var (
|
||||
err error
|
||||
slicerCache *xlsxSlicerCacheDefinition
|
||||
)
|
||||
f.Pkg.Range(func(k, v interface{}) bool {
|
||||
if strings.Contains(k.(string), "xl/slicerCaches/slicerCache") {
|
||||
slicerCache, err = f.slicerCacheReader(k.(string))
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if slicerCache.Name == slicerCacheName {
|
||||
opt.slicerCacheXML = k.(string)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
return slicerCache
|
||||
}
|
||||
|
||||
// getSlicers provides a function to get slicers options by given worksheet
|
||||
// name, slicer part relationship ID and drawing part path.
|
||||
func (f *File) getSlicers(sheet, rID, drawingXML string) ([]SlicerOptions, error) {
|
||||
var (
|
||||
opts []SlicerOptions
|
||||
sheetRelationshipsSlicerXML = f.getSheetRelationshipsTargetByID(sheet, rID)
|
||||
slicerXML = strings.ReplaceAll(sheetRelationshipsSlicerXML, "..", "xl")
|
||||
slicers, err = f.slicerReader(slicerXML)
|
||||
)
|
||||
if err != nil {
|
||||
return opts, err
|
||||
}
|
||||
for _, slicer := range slicers.Slicer {
|
||||
opt := SlicerOptions{
|
||||
slicerXML: slicerXML,
|
||||
slicerCacheName: slicer.Cache,
|
||||
slicerSheetName: sheet,
|
||||
slicerSheetRID: rID,
|
||||
drawingXML: drawingXML,
|
||||
Name: slicer.Name,
|
||||
Caption: slicer.Caption,
|
||||
DisplayHeader: slicer.ShowCaption,
|
||||
}
|
||||
slicerCache := f.getSlicerCache(slicer.Cache, &opt)
|
||||
if slicerCache == nil {
|
||||
return opts, err
|
||||
}
|
||||
if err := f.extractTableSlicer(slicerCache, &opt); err != nil {
|
||||
return opts, err
|
||||
}
|
||||
if err := f.extractPivotTableSlicer(slicerCache, &opt); err != nil {
|
||||
return opts, err
|
||||
}
|
||||
if err = f.extractSlicerCellAnchor(drawingXML, &opt); err != nil {
|
||||
return opts, err
|
||||
}
|
||||
opts = append(opts, opt)
|
||||
}
|
||||
return opts, err
|
||||
}
|
||||
|
||||
// extractTableSlicer extract table slicer options from slicer cache.
|
||||
func (f *File) extractTableSlicer(slicerCache *xlsxSlicerCacheDefinition, opt *SlicerOptions) error {
|
||||
if slicerCache.ExtLst != nil {
|
||||
tables, err := f.getTables()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ext := new(xlsxExt)
|
||||
_ = f.xmlNewDecoder(strings.NewReader(slicerCache.ExtLst.Ext)).Decode(ext)
|
||||
if ext.URI == ExtURISlicerCacheDefinition {
|
||||
tableSlicerCache := new(decodeTableSlicerCache)
|
||||
_ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(tableSlicerCache)
|
||||
opt.ItemDesc = tableSlicerCache.SortOrder == "descending"
|
||||
for sheetName, sheetTables := range tables {
|
||||
for _, table := range sheetTables {
|
||||
if tableSlicerCache.TableID == table.tID {
|
||||
opt.TableName = table.Name
|
||||
opt.TableSheet = sheetName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// extractPivotTableSlicer extract pivot table slicer options from slicer cache.
|
||||
func (f *File) extractPivotTableSlicer(slicerCache *xlsxSlicerCacheDefinition, opt *SlicerOptions) error {
|
||||
pivotTables, err := f.getPivotTables()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if slicerCache.PivotTables != nil {
|
||||
for _, pt := range slicerCache.PivotTables.PivotTable {
|
||||
opt.TableName = pt.Name
|
||||
for sheetName, sheetPivotTables := range pivotTables {
|
||||
for _, pivotTable := range sheetPivotTables {
|
||||
if opt.TableName == pivotTable.Name {
|
||||
opt.TableSheet = sheetName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if slicerCache.Data != nil && slicerCache.Data.Tabular != nil {
|
||||
opt.ItemDesc = slicerCache.Data.Tabular.SortOrder == "descending"
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// extractSlicerCellAnchor extract slicer drawing object from two cell anchor by
|
||||
// giving drawing part path and slicer options.
|
||||
func (f *File) extractSlicerCellAnchor(drawingXML string, opt *SlicerOptions) error {
|
||||
var (
|
||||
wsDr *xlsxWsDr
|
||||
deCellAnchor = new(decodeCellAnchor)
|
||||
deChoice = new(decodeChoice)
|
||||
err error
|
||||
)
|
||||
if wsDr, _, err = f.drawingParser(drawingXML); err != nil {
|
||||
return err
|
||||
}
|
||||
wsDr.mu.Lock()
|
||||
defer wsDr.mu.Unlock()
|
||||
cond := func(ac *xlsxAlternateContent) bool {
|
||||
if ac != nil {
|
||||
_ = f.xmlNewDecoder(strings.NewReader(ac.Content)).Decode(&deChoice)
|
||||
if deChoice.XMLNSSle15 == NameSpaceDrawingMLSlicerX15.Value || deChoice.XMLNSA14 == NameSpaceDrawingMLA14.Value {
|
||||
if deChoice.GraphicFrame.NvGraphicFramePr.CNvPr.Name == opt.Name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
for _, anchor := range wsDr.TwoCellAnchor {
|
||||
for _, ac := range anchor.AlternateContent {
|
||||
if cond(ac) {
|
||||
if anchor.From != nil {
|
||||
opt.Macro = deChoice.GraphicFrame.Macro
|
||||
if opt.Cell, err = CoordinatesToCellName(anchor.From.Col+1, anchor.From.Row+1); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
_ = f.xmlNewDecoder(strings.NewReader("<decodeCellAnchor>" + anchor.GraphicFrame + "</decodeCellAnchor>")).Decode(&deCellAnchor)
|
||||
for _, ac := range deCellAnchor.AlternateContent {
|
||||
if cond(ac) {
|
||||
if deCellAnchor.From != nil {
|
||||
opt.Macro = deChoice.GraphicFrame.Macro
|
||||
if opt.Cell, err = CoordinatesToCellName(deCellAnchor.From.Col+1, deCellAnchor.From.Row+1); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// getAllSlicers provides a function to get all slicers in a workbook.
|
||||
func (f *File) getAllSlicers() (map[string][]SlicerOptions, error) {
|
||||
slicers := map[string][]SlicerOptions{}
|
||||
for _, sheetName := range f.GetSheetList() {
|
||||
sles, err := f.GetSlicers(sheetName)
|
||||
e := ErrSheetNotExist{sheetName}
|
||||
if err != nil && err.Error() != newNotWorksheetError(sheetName).Error() && err.Error() != e.Error() {
|
||||
return slicers, err
|
||||
}
|
||||
slicers[sheetName] = append(slicers[sheetName], sles...)
|
||||
}
|
||||
return slicers, nil
|
||||
}
|
||||
|
||||
// DeleteSlicer provides the method to delete a slicer by a given slicer name.
|
||||
func (f *File) DeleteSlicer(name string) error {
|
||||
sles, err := f.getAllSlicers()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, slicers := range sles {
|
||||
for _, slicer := range slicers {
|
||||
if slicer.Name != name {
|
||||
continue
|
||||
}
|
||||
_ = f.deleteSlicer(slicer)
|
||||
return f.deleteSlicerCache(sles, slicer)
|
||||
}
|
||||
}
|
||||
return newNoExistSlicerError(name)
|
||||
}
|
||||
|
||||
// getSlicers provides a function to delete slicer by given slicer options.
|
||||
func (f *File) deleteSlicer(opts SlicerOptions) error {
|
||||
slicers, err := f.slicerReader(opts.slicerXML)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := 0; i < len(slicers.Slicer); i++ {
|
||||
if slicers.Slicer[i].Name == opts.Name {
|
||||
slicers.Slicer = append(slicers.Slicer[:i], slicers.Slicer[i+1:]...)
|
||||
i--
|
||||
}
|
||||
}
|
||||
if len(slicers.Slicer) == 0 {
|
||||
var (
|
||||
extLstBytes []byte
|
||||
ws, err = f.workSheetReader(opts.slicerSheetName)
|
||||
decodeExtLst = new(decodeExtLst)
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = f.xmlNewDecoder(strings.NewReader("<extLst>" + ws.ExtLst.Ext + "</extLst>")).
|
||||
Decode(decodeExtLst); err != nil && err != io.EOF {
|
||||
return err
|
||||
}
|
||||
for i, ext := range decodeExtLst.Ext {
|
||||
if ext.URI == ExtURISlicerListX14 || ext.URI == ExtURISlicerListX15 {
|
||||
slicerList := new(decodeSlicerList)
|
||||
_ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(slicerList)
|
||||
for _, slicer := range slicerList.Slicer {
|
||||
if slicer.RID == opts.slicerSheetRID {
|
||||
decodeExtLst.Ext = append(decodeExtLst.Ext[:i], decodeExtLst.Ext[i+1:]...)
|
||||
extLstBytes, err = xml.Marshal(decodeExtLst)
|
||||
ws.ExtLst = &xlsxExtLst{Ext: strings.TrimSuffix(strings.TrimPrefix(string(extLstBytes), "<extLst>"), "</extLst>")}
|
||||
f.Pkg.Delete(opts.slicerXML)
|
||||
_ = f.removeContentTypesPart(ContentTypeSlicer, "/"+opts.slicerXML)
|
||||
f.deleteSheetRelationships(opts.slicerSheetName, opts.slicerSheetRID)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
output, err := xml.Marshal(slicers)
|
||||
f.saveFileList(opts.slicerXML, output)
|
||||
return err
|
||||
}
|
||||
|
||||
// deleteSlicerCache provides a function to delete the slicer cache by giving
|
||||
// slicer options if the slicer cache is no longer used.
|
||||
func (f *File) deleteSlicerCache(sles map[string][]SlicerOptions, opts SlicerOptions) error {
|
||||
for _, slicers := range sles {
|
||||
for _, slicer := range slicers {
|
||||
if slicer.Name != opts.Name && slicer.slicerCacheName == opts.slicerCacheName {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := f.DeleteDefinedName(&DefinedName{Name: opts.slicerCacheName}); err != nil {
|
||||
return err
|
||||
}
|
||||
f.Pkg.Delete(opts.slicerCacheXML)
|
||||
return f.removeContentTypesPart(ContentTypeSlicerCache, "/"+opts.slicerCacheXML)
|
||||
}
|
||||
|
|
251
slicer_test.go
251
slicer_test.go
|
@ -5,12 +5,13 @@ import (
|
|||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAddSlicer(t *testing.T) {
|
||||
func TestSlicer(t *testing.T) {
|
||||
f := NewFile()
|
||||
disable, colName := false, "_!@#$%^&*()-+=|\\/<>"
|
||||
assert.NoError(t, f.SetCellValue("Sheet1", "B1", colName))
|
||||
|
@ -45,8 +46,29 @@ func TestAddSlicer(t *testing.T) {
|
|||
DisplayHeader: &disable,
|
||||
ItemDesc: true,
|
||||
}))
|
||||
// Test get table slicers
|
||||
slicers, err := f.GetSlicers("Sheet1")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "Column1", slicers[0].Name)
|
||||
assert.Equal(t, "E1", slicers[0].Cell)
|
||||
assert.Equal(t, "Sheet1", slicers[0].TableSheet)
|
||||
assert.Equal(t, "Table1", slicers[0].TableName)
|
||||
assert.Equal(t, "Column1", slicers[0].Caption)
|
||||
assert.Equal(t, "Column1 1", slicers[1].Name)
|
||||
assert.Equal(t, "I1", slicers[1].Cell)
|
||||
assert.Equal(t, "Sheet1", slicers[1].TableSheet)
|
||||
assert.Equal(t, "Table1", slicers[1].TableName)
|
||||
assert.Equal(t, "Column1", slicers[1].Caption)
|
||||
assert.Equal(t, colName, slicers[2].Name)
|
||||
assert.Equal(t, "M1", slicers[2].Cell)
|
||||
assert.Equal(t, "Sheet1", slicers[2].TableSheet)
|
||||
assert.Equal(t, "Table1", slicers[2].TableName)
|
||||
assert.Equal(t, colName, slicers[2].Caption)
|
||||
assert.Equal(t, "Button1_Click", slicers[2].Macro)
|
||||
assert.False(t, *slicers[2].DisplayHeader)
|
||||
assert.True(t, slicers[2].ItemDesc)
|
||||
// Test create two pivot tables in a new worksheet
|
||||
_, err := f.NewSheet("Sheet2")
|
||||
_, err = f.NewSheet("Sheet2")
|
||||
assert.NoError(t, err)
|
||||
// Create some data in a sheet
|
||||
month := []string{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}
|
||||
|
@ -116,6 +138,25 @@ func TestAddSlicer(t *testing.T) {
|
|||
Caption: "Region",
|
||||
ItemDesc: true,
|
||||
}))
|
||||
// Test get pivot table slicers
|
||||
slicers, err = f.GetSlicers("Sheet2")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "Month", slicers[0].Name)
|
||||
assert.Equal(t, "G42", slicers[0].Cell)
|
||||
assert.Equal(t, "Sheet2", slicers[0].TableSheet)
|
||||
assert.Equal(t, "PivotTable1", slicers[0].TableName)
|
||||
assert.Equal(t, "Month", slicers[0].Caption)
|
||||
assert.Equal(t, "Month 1", slicers[1].Name)
|
||||
assert.Equal(t, "K42", slicers[1].Cell)
|
||||
assert.Equal(t, "Sheet2", slicers[1].TableSheet)
|
||||
assert.Equal(t, "PivotTable1", slicers[1].TableName)
|
||||
assert.Equal(t, "Month", slicers[1].Caption)
|
||||
assert.Equal(t, "Region", slicers[2].Name)
|
||||
assert.Equal(t, "O42", slicers[2].Cell)
|
||||
assert.Equal(t, "Sheet2", slicers[2].TableSheet)
|
||||
assert.Equal(t, "PivotTable2", slicers[2].TableName)
|
||||
assert.Equal(t, "Region", slicers[2].Caption)
|
||||
assert.True(t, slicers[2].ItemDesc)
|
||||
// Test add a table slicer with empty slicer options
|
||||
assert.Equal(t, ErrParameterRequired, f.AddSlicer("Sheet1", nil))
|
||||
// Test add a table slicer with invalid slicer options
|
||||
|
@ -167,6 +208,48 @@ func TestAddSlicer(t *testing.T) {
|
|||
}), "XML syntax error on line 1: invalid UTF-8")
|
||||
assert.NoError(t, f.Close())
|
||||
|
||||
// Test open a workbook and get already exist slicers
|
||||
f, err = OpenFile(workbookPath)
|
||||
assert.NoError(t, err)
|
||||
slicers, err = f.GetSlicers("Sheet1")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "Column1", slicers[0].Name)
|
||||
assert.Equal(t, "E1", slicers[0].Cell)
|
||||
assert.Equal(t, "Sheet1", slicers[0].TableSheet)
|
||||
assert.Equal(t, "Table1", slicers[0].TableName)
|
||||
assert.Equal(t, "Column1", slicers[0].Caption)
|
||||
assert.Equal(t, "Column1 1", slicers[1].Name)
|
||||
assert.Equal(t, "I1", slicers[1].Cell)
|
||||
assert.Equal(t, "Sheet1", slicers[1].TableSheet)
|
||||
assert.Equal(t, "Table1", slicers[1].TableName)
|
||||
assert.Equal(t, "Column1", slicers[1].Caption)
|
||||
assert.Equal(t, colName, slicers[2].Name)
|
||||
assert.Equal(t, "M1", slicers[2].Cell)
|
||||
assert.Equal(t, "Sheet1", slicers[2].TableSheet)
|
||||
assert.Equal(t, "Table1", slicers[2].TableName)
|
||||
assert.Equal(t, colName, slicers[2].Caption)
|
||||
assert.Equal(t, "Button1_Click", slicers[2].Macro)
|
||||
assert.False(t, *slicers[2].DisplayHeader)
|
||||
assert.True(t, slicers[2].ItemDesc)
|
||||
slicers, err = f.GetSlicers("Sheet2")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "Month", slicers[0].Name)
|
||||
assert.Equal(t, "G42", slicers[0].Cell)
|
||||
assert.Equal(t, "Sheet2", slicers[0].TableSheet)
|
||||
assert.Equal(t, "PivotTable1", slicers[0].TableName)
|
||||
assert.Equal(t, "Month", slicers[0].Caption)
|
||||
assert.Equal(t, "Month 1", slicers[1].Name)
|
||||
assert.Equal(t, "K42", slicers[1].Cell)
|
||||
assert.Equal(t, "Sheet2", slicers[1].TableSheet)
|
||||
assert.Equal(t, "PivotTable1", slicers[1].TableName)
|
||||
assert.Equal(t, "Month", slicers[1].Caption)
|
||||
assert.Equal(t, "Region", slicers[2].Name)
|
||||
assert.Equal(t, "O42", slicers[2].Cell)
|
||||
assert.Equal(t, "Sheet2", slicers[2].TableSheet)
|
||||
assert.Equal(t, "PivotTable2", slicers[2].TableName)
|
||||
assert.Equal(t, "Region", slicers[2].Caption)
|
||||
assert.True(t, slicers[2].ItemDesc)
|
||||
|
||||
// Test add a pivot table slicer with workbook which contains timeline
|
||||
f, err = OpenFile(workbookPath)
|
||||
assert.NoError(t, err)
|
||||
|
@ -274,6 +357,113 @@ func TestAddSlicer(t *testing.T) {
|
|||
Caption: "Column1",
|
||||
}), "XML syntax error on line 1: invalid UTF-8")
|
||||
assert.NoError(t, f.Close())
|
||||
|
||||
f = NewFile()
|
||||
// Test get sheet slicers without slicer
|
||||
slicers, err = f.GetSlicers("Sheet1")
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, slicers)
|
||||
// Test get sheet slicers with not exist worksheet name
|
||||
_, err = f.GetSlicers("SheetN")
|
||||
assert.EqualError(t, err, "sheet SheetN does not exist")
|
||||
assert.NoError(t, f.Close())
|
||||
|
||||
f, err = OpenFile(workbookPath)
|
||||
assert.NoError(t, err)
|
||||
// Test get sheet slicers with unsupported charset slicer cache
|
||||
f.Pkg.Store("xl/slicerCaches/slicerCache1.xml", MacintoshCyrillicCharset)
|
||||
_, err = f.GetSlicers("Sheet1")
|
||||
assert.NoError(t, err)
|
||||
// Test get sheet slicers with unsupported charset slicer
|
||||
f.Pkg.Store("xl/slicers/slicer1.xml", MacintoshCyrillicCharset)
|
||||
_, err = f.GetSlicers("Sheet1")
|
||||
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
|
||||
// Test get sheet slicers with invalid worksheet extension list
|
||||
ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml")
|
||||
assert.True(t, ok)
|
||||
ws.(*xlsxWorksheet).ExtLst.Ext = "<>"
|
||||
_, err = f.GetSlicers("Sheet1")
|
||||
assert.Error(t, err)
|
||||
assert.NoError(t, f.Close())
|
||||
|
||||
f, err = OpenFile(workbookPath)
|
||||
assert.NoError(t, err)
|
||||
// Test get sheet slicers without slicer cache
|
||||
f.Pkg.Range(func(k, v interface{}) bool {
|
||||
if strings.Contains(k.(string), "xl/slicerCaches/slicerCache") {
|
||||
f.Pkg.Delete(k.(string))
|
||||
}
|
||||
return true
|
||||
})
|
||||
slicers, err = f.GetSlicers("Sheet1")
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, slicers)
|
||||
assert.NoError(t, f.Close())
|
||||
// Test open a workbook and get sheet slicer with invalid cell reference in the drawing part
|
||||
f, err = OpenFile(workbookPath)
|
||||
assert.NoError(t, err)
|
||||
f.Pkg.Store("xl/drawings/drawing1.xml", []byte(fmt.Sprintf(`<wsDr xmlns="%s"><twoCellAnchor><from><col>-1</col><row>-1</row></from><mc:AlternateContent><mc:Choice xmlns:sle15="%s"><graphicFrame><nvGraphicFramePr><cNvPr id="2" name="Column1"/></nvGraphicFramePr></graphicFrame></mc:Choice></mc:AlternateContent></twoCellAnchor></wsDr>`, NameSpaceDrawingMLSpreadSheet.Value, NameSpaceDrawingMLSlicerX15.Value)))
|
||||
_, err = f.GetSlicers("Sheet1")
|
||||
assert.Equal(t, newCoordinatesToCellNameError(0, 0), err)
|
||||
// Test get sheet slicer without slicer shape in the drawing part
|
||||
f.Drawings.Delete("xl/drawings/drawing1.xml")
|
||||
f.Pkg.Store("xl/drawings/drawing1.xml", []byte(fmt.Sprintf(`<wsDr xmlns="%s"><twoCellAnchor/></wsDr>`, NameSpaceDrawingMLSpreadSheet.Value)))
|
||||
_, err = f.GetSlicers("Sheet1")
|
||||
assert.NoError(t, err)
|
||||
f.Drawings.Delete("xl/drawings/drawing1.xml")
|
||||
// Test get sheet slicers with unsupported charset drawing part
|
||||
f.Pkg.Store("xl/drawings/drawing1.xml", MacintoshCyrillicCharset)
|
||||
_, err = f.GetSlicers("Sheet1")
|
||||
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
|
||||
// Test get sheet slicers with unsupported charset table
|
||||
f.Pkg.Store("xl/tables/table1.xml", MacintoshCyrillicCharset)
|
||||
_, err = f.GetSlicers("Sheet1")
|
||||
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
|
||||
// Test get sheet slicers with unsupported charset pivot table
|
||||
f.Pkg.Store("xl/pivotTables/pivotTable1.xml", MacintoshCyrillicCharset)
|
||||
_, err = f.GetSlicers("Sheet2")
|
||||
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
|
||||
assert.NoError(t, f.Close())
|
||||
|
||||
// Test create a workbook and get sheet slicer with invalid cell reference in the drawing part
|
||||
f = NewFile()
|
||||
assert.NoError(t, f.AddTable("Sheet1", &Table{
|
||||
Name: "Table1",
|
||||
Range: "A1:D5",
|
||||
}))
|
||||
assert.NoError(t, f.AddSlicer("Sheet1", &SlicerOptions{
|
||||
Name: "Column1",
|
||||
Cell: "E1",
|
||||
TableSheet: "Sheet1",
|
||||
TableName: "Table1",
|
||||
Caption: "Column1",
|
||||
}))
|
||||
drawing, ok := f.Drawings.Load("xl/drawings/drawing1.xml")
|
||||
assert.True(t, ok)
|
||||
drawing.(*xlsxWsDr).TwoCellAnchor[0].From = &xlsxFrom{Col: -1, Row: -1}
|
||||
_, err = f.GetSlicers("Sheet1")
|
||||
assert.Equal(t, newCoordinatesToCellNameError(0, 0), err)
|
||||
assert.NoError(t, f.Close())
|
||||
|
||||
// Test open a workbook and delete slicers
|
||||
f, err = OpenFile(workbookPath)
|
||||
assert.NoError(t, err)
|
||||
for _, name := range []string{colName, "Column1 1", "Column1"} {
|
||||
assert.NoError(t, f.DeleteSlicer(name))
|
||||
}
|
||||
for _, name := range []string{"Month", "Month 1", "Region"} {
|
||||
assert.NoError(t, f.DeleteSlicer(name))
|
||||
}
|
||||
// Test delete slicer with no exits slicer name
|
||||
assert.Equal(t, newNoExistSlicerError("x"), f.DeleteSlicer("x"))
|
||||
assert.NoError(t, f.Close())
|
||||
|
||||
// Test open a workbook and delete sheet slicer with unsupported charset slicer cache
|
||||
f, err = OpenFile(workbookPath)
|
||||
assert.NoError(t, err)
|
||||
f.Pkg.Store("xl/slicers/slicer1.xml", MacintoshCyrillicCharset)
|
||||
assert.EqualError(t, f.DeleteSlicer("Column1"), "XML syntax error on line 1: invalid UTF-8")
|
||||
assert.NoError(t, f.Close())
|
||||
}
|
||||
|
||||
func TestAddSheetSlicer(t *testing.T) {
|
||||
|
@ -296,36 +486,81 @@ func TestAddSheetTableSlicer(t *testing.T) {
|
|||
func TestSetSlicerCache(t *testing.T) {
|
||||
f := NewFile()
|
||||
f.Pkg.Store("xl/slicerCaches/slicerCache1.xml", MacintoshCyrillicCharset)
|
||||
_, err := f.setSlicerCache("Sheet1", 1, &SlicerOptions{}, &Table{}, nil)
|
||||
_, err := f.setSlicerCache(1, &SlicerOptions{}, &Table{}, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, f.Close())
|
||||
|
||||
f = NewFile()
|
||||
|
||||
f.Pkg.Store("xl/slicerCaches/slicerCache2.xml", []byte(fmt.Sprintf(`<slicerCacheDefinition xmlns="%s" name="Slicer2" sourceName="B1"><extLst><ext uri="%s"/></extLst></slicerCacheDefinition>`, NameSpaceSpreadSheetX14.Value, ExtURISlicerCacheDefinition)))
|
||||
_, err = f.setSlicerCache("Sheet1", 1, &SlicerOptions{}, &Table{}, nil)
|
||||
_, err = f.setSlicerCache(1, &SlicerOptions{}, &Table{}, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, f.Close())
|
||||
|
||||
f = NewFile()
|
||||
f.Pkg.Store("xl/slicerCaches/slicerCache2.xml", []byte(fmt.Sprintf(`<slicerCacheDefinition xmlns="%s" name="Slicer1" sourceName="B1"><extLst><ext uri="%s"/></extLst></slicerCacheDefinition>`, NameSpaceSpreadSheetX14.Value, ExtURISlicerCacheDefinition)))
|
||||
_, err = f.setSlicerCache("Sheet1", 1, &SlicerOptions{}, &Table{}, nil)
|
||||
_, err = f.setSlicerCache(1, &SlicerOptions{}, &Table{}, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, f.Close())
|
||||
|
||||
f = NewFile()
|
||||
f.Pkg.Store("xl/slicerCaches/slicerCache2.xml", []byte(fmt.Sprintf(`<slicerCacheDefinition xmlns="%s" name="Slicer1" sourceName="B1"><extLst><ext uri="%s"><tableSlicerCache tableId="1" column="2"/></ext></extLst></slicerCacheDefinition>`, NameSpaceSpreadSheetX14.Value, ExtURISlicerCacheDefinition)))
|
||||
_, err = f.setSlicerCache("Sheet1", 1, &SlicerOptions{}, &Table{tID: 1}, nil)
|
||||
_, err = f.setSlicerCache(1, &SlicerOptions{}, &Table{tID: 1}, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, f.Close())
|
||||
|
||||
f = NewFile()
|
||||
f.Pkg.Store("xl/slicerCaches/slicerCache2.xml", []byte(fmt.Sprintf(`<slicerCacheDefinition xmlns="%s" name="Slicer1" sourceName="B1"></slicerCacheDefinition>`, NameSpaceSpreadSheetX14.Value)))
|
||||
_, err = f.setSlicerCache("Sheet1", 1, &SlicerOptions{}, &Table{tID: 1}, nil)
|
||||
_, err = f.setSlicerCache(1, &SlicerOptions{}, &Table{tID: 1}, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, f.Close())
|
||||
}
|
||||
|
||||
func TestDeleteSlicer(t *testing.T) {
|
||||
f, slicerXML := NewFile(), "xl/slicers/slicer1.xml"
|
||||
assert.NoError(t, f.AddTable("Sheet1", &Table{
|
||||
Name: "Table1",
|
||||
Range: "A1:D5",
|
||||
}))
|
||||
assert.NoError(t, f.AddSlicer("Sheet1", &SlicerOptions{
|
||||
Name: "Column1",
|
||||
Cell: "E1",
|
||||
TableSheet: "Sheet1",
|
||||
TableName: "Table1",
|
||||
Caption: "Column1",
|
||||
}))
|
||||
// Test delete sheet slicers with invalid worksheet extension list
|
||||
ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
|
||||
assert.True(t, ok)
|
||||
ws.(*xlsxWorksheet).ExtLst.Ext = "<>"
|
||||
assert.Error(t, f.deleteSlicer(SlicerOptions{
|
||||
slicerXML: slicerXML,
|
||||
slicerSheetName: "Sheet1",
|
||||
Name: "Column1",
|
||||
}))
|
||||
// Test delete slicer with unsupported charset worksheet
|
||||
f.Sheet.Delete("xl/worksheets/sheet1.xml")
|
||||
f.Pkg.Store("xl/worksheets/sheet1.xml", MacintoshCyrillicCharset)
|
||||
assert.EqualError(t, f.deleteSlicer(SlicerOptions{
|
||||
slicerXML: slicerXML,
|
||||
slicerSheetName: "Sheet1",
|
||||
Name: "Column1",
|
||||
}), "XML syntax error on line 1: invalid UTF-8")
|
||||
// Test delete slicer with unsupported charset slicer
|
||||
f.Pkg.Store(slicerXML, MacintoshCyrillicCharset)
|
||||
assert.EqualError(t, f.deleteSlicer(SlicerOptions{slicerXML: slicerXML}), "XML syntax error on line 1: invalid UTF-8")
|
||||
assert.NoError(t, f.Close())
|
||||
}
|
||||
|
||||
func TestDeleteSlicerCache(t *testing.T) {
|
||||
f := NewFile()
|
||||
// Test delete slicer cache with unsupported charset workbook
|
||||
f.WorkBook = nil
|
||||
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
|
||||
assert.EqualError(t, f.deleteSlicerCache(nil, SlicerOptions{}), "XML syntax error on line 1: invalid UTF-8")
|
||||
assert.NoError(t, f.Close())
|
||||
}
|
||||
|
||||
func TestAddSlicerCache(t *testing.T) {
|
||||
f := NewFile()
|
||||
f.ContentTypes = nil
|
||||
|
@ -362,7 +597,7 @@ func TestAddWorkbookSlicerCache(t *testing.T) {
|
|||
// Test add a workbook slicer cache with unsupported charset workbook
|
||||
f := NewFile()
|
||||
f.WorkBook = nil
|
||||
f.Pkg.Store("xl/workbook.xml", MacintoshCyrillicCharset)
|
||||
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
|
||||
assert.EqualError(t, f.addWorkbookSlicerCache(1, ExtURISlicerCachesX15), "XML syntax error on line 1: invalid UTF-8")
|
||||
assert.NoError(t, f.Close())
|
||||
}
|
||||
|
|
682
sparkline.go
682
sparkline.go
|
@ -18,350 +18,352 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
// sparklineGroupPresets defined the list of sparkline group to create
|
||||
// x14:sparklineGroups element by given sparkline style ID.
|
||||
var sparklineGroupPresets = []*xlsxX14SparklineGroup{
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(4), Tint: -0.499984740745262},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(5)},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(4), Tint: -0.499984740745262},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(4), Tint: 0.39997558519241921},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(4), Tint: 0.39997558519241921},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(4)},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(4)},
|
||||
}, // 0
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(4), Tint: -0.499984740745262},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(5)},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(4), Tint: -0.499984740745262},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(4), Tint: 0.39997558519241921},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(4), Tint: 0.39997558519241921},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(4)},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(4)},
|
||||
}, // 1
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(5), Tint: -0.499984740745262},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(6)},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(5), Tint: -0.499984740745262},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(5), Tint: 0.39997558519241921},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(5), Tint: 0.39997558519241921},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(5)},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(5)},
|
||||
}, // 2
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(6), Tint: -0.499984740745262},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(7)},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(6), Tint: -0.499984740745262},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(6), Tint: 0.39997558519241921},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(6), Tint: 0.39997558519241921},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(6)},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(6)},
|
||||
}, // 3
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(7), Tint: -0.499984740745262},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(8)},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(7), Tint: -0.499984740745262},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(7), Tint: 0.39997558519241921},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(7), Tint: 0.39997558519241921},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(7)},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(7)},
|
||||
}, // 4
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(8), Tint: -0.499984740745262},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(9)},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(8), Tint: -0.499984740745262},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(8), Tint: 0.39997558519241921},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(8), Tint: 0.39997558519241921},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(8)},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(8)},
|
||||
}, // 5
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(9), Tint: -0.499984740745262},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(4)},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(9), Tint: -0.499984740745262},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(9), Tint: 0.39997558519241921},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(9), Tint: 0.39997558519241921},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(9)},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(9)},
|
||||
}, // 6
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(5)},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(5), Tint: -0.249977111117893},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(5), Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(5), Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(5)},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(5)},
|
||||
}, // 7
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(5), Tint: -0.249977111117893},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(6)},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893},
|
||||
}, // 8
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(7)},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893},
|
||||
}, // 9
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(8)},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893},
|
||||
}, // 10
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(9)},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893},
|
||||
}, // 11
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(4)},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893},
|
||||
}, // 12
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(4)},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(5)},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893},
|
||||
}, // 13
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(5)},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(6)},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(5), Tint: -0.249977111117893},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(5), Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(5), Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(5), Tint: -0.249977111117893},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(5), Tint: -0.249977111117893},
|
||||
}, // 14
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(6)},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(7)},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893},
|
||||
}, // 15
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(7)},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(8)},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893},
|
||||
}, // 16
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(8)},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(9)},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893},
|
||||
}, // 17
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(9)},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(4)},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893},
|
||||
}, // 18
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(4), Tint: 0.39997558519241921},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(0), Tint: -0.499984740745262},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(4), Tint: 0.79998168889431442},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(4), Tint: -0.499984740745262},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(4), Tint: -0.499984740745262},
|
||||
}, // 19
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(5), Tint: 0.39997558519241921},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(0), Tint: -0.499984740745262},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(5), Tint: 0.79998168889431442},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(5), Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(5), Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(5), Tint: -0.499984740745262},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(5), Tint: -0.499984740745262},
|
||||
}, // 20
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(6), Tint: 0.39997558519241921},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(0), Tint: -0.499984740745262},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(6), Tint: 0.79998168889431442},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(6), Tint: -0.499984740745262},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(6), Tint: -0.499984740745262},
|
||||
}, // 21
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(7), Tint: 0.39997558519241921},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(0), Tint: -0.499984740745262},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(7), Tint: 0.79998168889431442},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(7), Tint: -0.499984740745262},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(7), Tint: -0.499984740745262},
|
||||
}, // 22
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(8), Tint: 0.39997558519241921},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(0), Tint: -0.499984740745262},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(8), Tint: 0.79998168889431442},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(8), Tint: -0.499984740745262},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(8), Tint: -0.499984740745262},
|
||||
}, // 23
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(9), Tint: 0.39997558519241921},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(0), Tint: -0.499984740745262},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(9), Tint: 0.79998168889431442},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(9), Tint: -0.499984740745262},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(9), Tint: -0.499984740745262},
|
||||
}, // 24
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(1), Tint: 0.499984740745262},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(1), Tint: 0.249977111117893},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(1), Tint: 0.249977111117893},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(1), Tint: 0.249977111117893},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(1), Tint: 0.249977111117893},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(1), Tint: 0.249977111117893},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(1), Tint: 0.249977111117893},
|
||||
}, // 25
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(1), Tint: 0.34998626667073579},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(0), Tint: 0.249977111117893},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(0), Tint: 0.249977111117893},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(0), Tint: 0.249977111117893},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(0), Tint: 0.249977111117893},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(0), Tint: 0.249977111117893},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(0), Tint: 0.249977111117893},
|
||||
}, // 26
|
||||
{
|
||||
ColorSeries: &xlsxColor{RGB: "FF323232"},
|
||||
ColorNegative: &xlsxColor{RGB: "FFD00000"},
|
||||
ColorMarkers: &xlsxColor{RGB: "FFD00000"},
|
||||
ColorFirst: &xlsxColor{RGB: "FFD00000"},
|
||||
ColorLast: &xlsxColor{RGB: "FFD00000"},
|
||||
ColorHigh: &xlsxColor{RGB: "FFD00000"},
|
||||
ColorLow: &xlsxColor{RGB: "FFD00000"},
|
||||
}, // 27
|
||||
{
|
||||
ColorSeries: &xlsxColor{RGB: "FF000000"},
|
||||
ColorNegative: &xlsxColor{RGB: "FF0070C0"},
|
||||
ColorMarkers: &xlsxColor{RGB: "FF0070C0"},
|
||||
ColorFirst: &xlsxColor{RGB: "FF0070C0"},
|
||||
ColorLast: &xlsxColor{RGB: "FF0070C0"},
|
||||
ColorHigh: &xlsxColor{RGB: "FF0070C0"},
|
||||
ColorLow: &xlsxColor{RGB: "FF0070C0"},
|
||||
}, // 28
|
||||
{
|
||||
ColorSeries: &xlsxColor{RGB: "FF376092"},
|
||||
ColorNegative: &xlsxColor{RGB: "FFD00000"},
|
||||
ColorMarkers: &xlsxColor{RGB: "FFD00000"},
|
||||
ColorFirst: &xlsxColor{RGB: "FFD00000"},
|
||||
ColorLast: &xlsxColor{RGB: "FFD00000"},
|
||||
ColorHigh: &xlsxColor{RGB: "FFD00000"},
|
||||
ColorLow: &xlsxColor{RGB: "FFD00000"},
|
||||
}, // 29
|
||||
{
|
||||
ColorSeries: &xlsxColor{RGB: "FF0070C0"},
|
||||
ColorNegative: &xlsxColor{RGB: "FF000000"},
|
||||
ColorMarkers: &xlsxColor{RGB: "FF000000"},
|
||||
ColorFirst: &xlsxColor{RGB: "FF000000"},
|
||||
ColorLast: &xlsxColor{RGB: "FF000000"},
|
||||
ColorHigh: &xlsxColor{RGB: "FF000000"},
|
||||
ColorLow: &xlsxColor{RGB: "FF000000"},
|
||||
}, // 30
|
||||
{
|
||||
ColorSeries: &xlsxColor{RGB: "FF5F5F5F"},
|
||||
ColorNegative: &xlsxColor{RGB: "FFFFB620"},
|
||||
ColorMarkers: &xlsxColor{RGB: "FFD70077"},
|
||||
ColorFirst: &xlsxColor{RGB: "FF5687C2"},
|
||||
ColorLast: &xlsxColor{RGB: "FF359CEB"},
|
||||
ColorHigh: &xlsxColor{RGB: "FF56BE79"},
|
||||
ColorLow: &xlsxColor{RGB: "FFFF5055"},
|
||||
}, // 31
|
||||
{
|
||||
ColorSeries: &xlsxColor{RGB: "FF5687C2"},
|
||||
ColorNegative: &xlsxColor{RGB: "FFFFB620"},
|
||||
ColorMarkers: &xlsxColor{RGB: "FFD70077"},
|
||||
ColorFirst: &xlsxColor{RGB: "FF777777"},
|
||||
ColorLast: &xlsxColor{RGB: "FF359CEB"},
|
||||
ColorHigh: &xlsxColor{RGB: "FF56BE79"},
|
||||
ColorLow: &xlsxColor{RGB: "FFFF5055"},
|
||||
}, // 32
|
||||
{
|
||||
ColorSeries: &xlsxColor{RGB: "FFC6EFCE"},
|
||||
ColorNegative: &xlsxColor{RGB: "FFFFC7CE"},
|
||||
ColorMarkers: &xlsxColor{RGB: "FF8CADD6"},
|
||||
ColorFirst: &xlsxColor{RGB: "FFFFDC47"},
|
||||
ColorLast: &xlsxColor{RGB: "FFFFEB9C"},
|
||||
ColorHigh: &xlsxColor{RGB: "FF60D276"},
|
||||
ColorLow: &xlsxColor{RGB: "FFFF5367"},
|
||||
}, // 33
|
||||
{
|
||||
ColorSeries: &xlsxColor{RGB: "FF00B050"},
|
||||
ColorNegative: &xlsxColor{RGB: "FFFF0000"},
|
||||
ColorMarkers: &xlsxColor{RGB: "FF0070C0"},
|
||||
ColorFirst: &xlsxColor{RGB: "FFFFC000"},
|
||||
ColorLast: &xlsxColor{RGB: "FFFFC000"},
|
||||
ColorHigh: &xlsxColor{RGB: "FF00B050"},
|
||||
ColorLow: &xlsxColor{RGB: "FFFF0000"},
|
||||
}, // 34
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(3)},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(9)},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(8)},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(4)},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(5)},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(6)},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(7)},
|
||||
}, // 35
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(1)},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(9)},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(8)},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(4)},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(5)},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(6)},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(7)},
|
||||
}, // 36
|
||||
// getSparklineGroupPresets returns the preset list of sparkline group to create
|
||||
// x14:sparklineGroups element.
|
||||
func getSparklineGroupPresets() []*xlsxX14SparklineGroup {
|
||||
return []*xlsxX14SparklineGroup{
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(4), Tint: -0.499984740745262},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(5)},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(4), Tint: -0.499984740745262},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(4), Tint: 0.39997558519241921},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(4), Tint: 0.39997558519241921},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(4)},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(4)},
|
||||
}, // 0
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(4), Tint: -0.499984740745262},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(5)},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(4), Tint: -0.499984740745262},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(4), Tint: 0.39997558519241921},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(4), Tint: 0.39997558519241921},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(4)},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(4)},
|
||||
}, // 1
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(5), Tint: -0.499984740745262},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(6)},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(5), Tint: -0.499984740745262},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(5), Tint: 0.39997558519241921},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(5), Tint: 0.39997558519241921},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(5)},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(5)},
|
||||
}, // 2
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(6), Tint: -0.499984740745262},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(7)},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(6), Tint: -0.499984740745262},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(6), Tint: 0.39997558519241921},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(6), Tint: 0.39997558519241921},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(6)},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(6)},
|
||||
}, // 3
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(7), Tint: -0.499984740745262},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(8)},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(7), Tint: -0.499984740745262},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(7), Tint: 0.39997558519241921},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(7), Tint: 0.39997558519241921},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(7)},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(7)},
|
||||
}, // 4
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(8), Tint: -0.499984740745262},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(9)},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(8), Tint: -0.499984740745262},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(8), Tint: 0.39997558519241921},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(8), Tint: 0.39997558519241921},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(8)},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(8)},
|
||||
}, // 5
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(9), Tint: -0.499984740745262},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(4)},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(9), Tint: -0.499984740745262},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(9), Tint: 0.39997558519241921},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(9), Tint: 0.39997558519241921},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(9)},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(9)},
|
||||
}, // 6
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(5)},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(5), Tint: -0.249977111117893},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(5), Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(5), Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(5)},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(5)},
|
||||
}, // 7
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(5), Tint: -0.249977111117893},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(6)},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893},
|
||||
}, // 8
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(7)},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893},
|
||||
}, // 9
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(8)},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893},
|
||||
}, // 10
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(9)},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893},
|
||||
}, // 11
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(4)},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893},
|
||||
}, // 12
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(4)},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(5)},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893},
|
||||
}, // 13
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(5)},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(6)},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(5), Tint: -0.249977111117893},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(5), Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(5), Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(5), Tint: -0.249977111117893},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(5), Tint: -0.249977111117893},
|
||||
}, // 14
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(6)},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(7)},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893},
|
||||
}, // 15
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(7)},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(8)},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893},
|
||||
}, // 16
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(8)},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(9)},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893},
|
||||
}, // 17
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(9)},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(4)},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893},
|
||||
}, // 18
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(4), Tint: 0.39997558519241921},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(0), Tint: -0.499984740745262},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(4), Tint: 0.79998168889431442},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(4), Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(4), Tint: -0.499984740745262},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(4), Tint: -0.499984740745262},
|
||||
}, // 19
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(5), Tint: 0.39997558519241921},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(0), Tint: -0.499984740745262},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(5), Tint: 0.79998168889431442},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(5), Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(5), Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(5), Tint: -0.499984740745262},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(5), Tint: -0.499984740745262},
|
||||
}, // 20
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(6), Tint: 0.39997558519241921},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(0), Tint: -0.499984740745262},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(6), Tint: 0.79998168889431442},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(6), Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(6), Tint: -0.499984740745262},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(6), Tint: -0.499984740745262},
|
||||
}, // 21
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(7), Tint: 0.39997558519241921},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(0), Tint: -0.499984740745262},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(7), Tint: 0.79998168889431442},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(7), Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(7), Tint: -0.499984740745262},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(7), Tint: -0.499984740745262},
|
||||
}, // 22
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(8), Tint: 0.39997558519241921},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(0), Tint: -0.499984740745262},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(8), Tint: 0.79998168889431442},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(8), Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(8), Tint: -0.499984740745262},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(8), Tint: -0.499984740745262},
|
||||
}, // 23
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(9), Tint: 0.39997558519241921},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(0), Tint: -0.499984740745262},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(9), Tint: 0.79998168889431442},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(9), Tint: -0.249977111117893},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(9), Tint: -0.499984740745262},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(9), Tint: -0.499984740745262},
|
||||
}, // 24
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(1), Tint: 0.499984740745262},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(1), Tint: 0.249977111117893},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(1), Tint: 0.249977111117893},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(1), Tint: 0.249977111117893},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(1), Tint: 0.249977111117893},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(1), Tint: 0.249977111117893},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(1), Tint: 0.249977111117893},
|
||||
}, // 25
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(1), Tint: 0.34998626667073579},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(0), Tint: 0.249977111117893},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(0), Tint: 0.249977111117893},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(0), Tint: 0.249977111117893},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(0), Tint: 0.249977111117893},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(0), Tint: 0.249977111117893},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(0), Tint: 0.249977111117893},
|
||||
}, // 26
|
||||
{
|
||||
ColorSeries: &xlsxColor{RGB: "FF323232"},
|
||||
ColorNegative: &xlsxColor{RGB: "FFD00000"},
|
||||
ColorMarkers: &xlsxColor{RGB: "FFD00000"},
|
||||
ColorFirst: &xlsxColor{RGB: "FFD00000"},
|
||||
ColorLast: &xlsxColor{RGB: "FFD00000"},
|
||||
ColorHigh: &xlsxColor{RGB: "FFD00000"},
|
||||
ColorLow: &xlsxColor{RGB: "FFD00000"},
|
||||
}, // 27
|
||||
{
|
||||
ColorSeries: &xlsxColor{RGB: "FF000000"},
|
||||
ColorNegative: &xlsxColor{RGB: "FF0070C0"},
|
||||
ColorMarkers: &xlsxColor{RGB: "FF0070C0"},
|
||||
ColorFirst: &xlsxColor{RGB: "FF0070C0"},
|
||||
ColorLast: &xlsxColor{RGB: "FF0070C0"},
|
||||
ColorHigh: &xlsxColor{RGB: "FF0070C0"},
|
||||
ColorLow: &xlsxColor{RGB: "FF0070C0"},
|
||||
}, // 28
|
||||
{
|
||||
ColorSeries: &xlsxColor{RGB: "FF376092"},
|
||||
ColorNegative: &xlsxColor{RGB: "FFD00000"},
|
||||
ColorMarkers: &xlsxColor{RGB: "FFD00000"},
|
||||
ColorFirst: &xlsxColor{RGB: "FFD00000"},
|
||||
ColorLast: &xlsxColor{RGB: "FFD00000"},
|
||||
ColorHigh: &xlsxColor{RGB: "FFD00000"},
|
||||
ColorLow: &xlsxColor{RGB: "FFD00000"},
|
||||
}, // 29
|
||||
{
|
||||
ColorSeries: &xlsxColor{RGB: "FF0070C0"},
|
||||
ColorNegative: &xlsxColor{RGB: "FF000000"},
|
||||
ColorMarkers: &xlsxColor{RGB: "FF000000"},
|
||||
ColorFirst: &xlsxColor{RGB: "FF000000"},
|
||||
ColorLast: &xlsxColor{RGB: "FF000000"},
|
||||
ColorHigh: &xlsxColor{RGB: "FF000000"},
|
||||
ColorLow: &xlsxColor{RGB: "FF000000"},
|
||||
}, // 30
|
||||
{
|
||||
ColorSeries: &xlsxColor{RGB: "FF5F5F5F"},
|
||||
ColorNegative: &xlsxColor{RGB: "FFFFB620"},
|
||||
ColorMarkers: &xlsxColor{RGB: "FFD70077"},
|
||||
ColorFirst: &xlsxColor{RGB: "FF5687C2"},
|
||||
ColorLast: &xlsxColor{RGB: "FF359CEB"},
|
||||
ColorHigh: &xlsxColor{RGB: "FF56BE79"},
|
||||
ColorLow: &xlsxColor{RGB: "FFFF5055"},
|
||||
}, // 31
|
||||
{
|
||||
ColorSeries: &xlsxColor{RGB: "FF5687C2"},
|
||||
ColorNegative: &xlsxColor{RGB: "FFFFB620"},
|
||||
ColorMarkers: &xlsxColor{RGB: "FFD70077"},
|
||||
ColorFirst: &xlsxColor{RGB: "FF777777"},
|
||||
ColorLast: &xlsxColor{RGB: "FF359CEB"},
|
||||
ColorHigh: &xlsxColor{RGB: "FF56BE79"},
|
||||
ColorLow: &xlsxColor{RGB: "FFFF5055"},
|
||||
}, // 32
|
||||
{
|
||||
ColorSeries: &xlsxColor{RGB: "FFC6EFCE"},
|
||||
ColorNegative: &xlsxColor{RGB: "FFFFC7CE"},
|
||||
ColorMarkers: &xlsxColor{RGB: "FF8CADD6"},
|
||||
ColorFirst: &xlsxColor{RGB: "FFFFDC47"},
|
||||
ColorLast: &xlsxColor{RGB: "FFFFEB9C"},
|
||||
ColorHigh: &xlsxColor{RGB: "FF60D276"},
|
||||
ColorLow: &xlsxColor{RGB: "FFFF5367"},
|
||||
}, // 33
|
||||
{
|
||||
ColorSeries: &xlsxColor{RGB: "FF00B050"},
|
||||
ColorNegative: &xlsxColor{RGB: "FFFF0000"},
|
||||
ColorMarkers: &xlsxColor{RGB: "FF0070C0"},
|
||||
ColorFirst: &xlsxColor{RGB: "FFFFC000"},
|
||||
ColorLast: &xlsxColor{RGB: "FFFFC000"},
|
||||
ColorHigh: &xlsxColor{RGB: "FF00B050"},
|
||||
ColorLow: &xlsxColor{RGB: "FFFF0000"},
|
||||
}, // 34
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(3)},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(9)},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(8)},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(4)},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(5)},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(6)},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(7)},
|
||||
}, // 35
|
||||
{
|
||||
ColorSeries: &xlsxColor{Theme: intPtr(1)},
|
||||
ColorNegative: &xlsxColor{Theme: intPtr(9)},
|
||||
ColorMarkers: &xlsxColor{Theme: intPtr(8)},
|
||||
ColorFirst: &xlsxColor{Theme: intPtr(4)},
|
||||
ColorLast: &xlsxColor{Theme: intPtr(5)},
|
||||
ColorHigh: &xlsxColor{Theme: intPtr(6)},
|
||||
ColorLow: &xlsxColor{Theme: intPtr(7)},
|
||||
}, // 36
|
||||
}
|
||||
}
|
||||
|
||||
// AddSparkline provides a function to add sparklines to the worksheet by
|
||||
// given formatting options. Sparklines are small charts that fit in a single
|
||||
// cell and are used to show trends in data. Sparklines are a feature of Excel
|
||||
// 2010 and later only. You can write them to an XLSX file that can be read by
|
||||
// Excel 2007, but they won't be displayed. For example, add a grouped
|
||||
// sparkline. Changes are applied to all three:
|
||||
// 2010 and later only. You can write them to workbook that can be read by Excel
|
||||
// 2007, but they won't be displayed. For example, add a grouped sparkline.
|
||||
// Changes are applied to all three:
|
||||
//
|
||||
// err := f.AddSparkline("Sheet1", &excelize.SparklineOptions{
|
||||
// Location: []string{"A1", "A2", "A3"},
|
||||
|
@ -412,7 +414,7 @@ func (f *File) AddSparkline(sheet string, opts *SparklineOptions) error {
|
|||
}
|
||||
sparkType = specifiedSparkTypes
|
||||
}
|
||||
group = sparklineGroupPresets[opts.Style]
|
||||
group = getSparklineGroupPresets()[opts.Style]
|
||||
group.Type = sparkType
|
||||
group.ColorAxis = &xlsxColor{RGB: "FF000000"}
|
||||
group.DisplayEmptyCellsAs = "gap"
|
||||
|
|
14
stream.go
14
stream.go
|
@ -184,7 +184,7 @@ func (sw *StreamWriter) AddTable(table *Table) error {
|
|||
}
|
||||
|
||||
// Correct table reference range, such correct C1:B3 to B1:C3.
|
||||
ref, err := sw.file.coordinatesToRangeRef(coordinates)
|
||||
ref, err := coordinatesToRangeRef(coordinates)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -290,7 +290,7 @@ func (sw *StreamWriter) getRowValues(hRow, hCol, vCol int) (res []string, err er
|
|||
}
|
||||
}
|
||||
|
||||
// Check if the token is an XLSX row with the matching row number.
|
||||
// Check if the token is an worksheet row with the matching row number.
|
||||
func getRowElement(token xml.Token, hRow int) (startElement xml.StartElement, ok bool) {
|
||||
startElement, ok = token.(xml.StartElement)
|
||||
if !ok {
|
||||
|
@ -527,11 +527,11 @@ func (sw *StreamWriter) setCellValFunc(c *xlsxC, val interface{}) error {
|
|||
var err error
|
||||
switch val := val.(type) {
|
||||
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
|
||||
err = setCellIntFunc(c, val)
|
||||
setCellIntFunc(c, val)
|
||||
case float32:
|
||||
c.T, c.V = setCellFloat(float64(val), -1, 32)
|
||||
c.setCellFloat(float64(val), -1, 32)
|
||||
case float64:
|
||||
c.T, c.V = setCellFloat(val, -1, 64)
|
||||
c.setCellFloat(val, -1, 64)
|
||||
case string:
|
||||
c.setCellValue(val)
|
||||
case []byte:
|
||||
|
@ -554,7 +554,7 @@ func (sw *StreamWriter) setCellValFunc(c *xlsxC, val interface{}) error {
|
|||
}
|
||||
|
||||
// setCellIntFunc is a wrapper of SetCellInt.
|
||||
func setCellIntFunc(c *xlsxC, val interface{}) (err error) {
|
||||
func setCellIntFunc(c *xlsxC, val interface{}) {
|
||||
switch val := val.(type) {
|
||||
case int:
|
||||
c.T, c.V = setCellInt(val)
|
||||
|
@ -576,9 +576,7 @@ func setCellIntFunc(c *xlsxC, val interface{}) (err error) {
|
|||
c.T, c.V = setCellUint(uint64(val))
|
||||
case uint64:
|
||||
c.T, c.V = setCellUint(val)
|
||||
default:
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// writeCell constructs a cell XML and writes it to the buffer.
|
||||
|
|
|
@ -3,6 +3,8 @@ package excelize
|
|||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
@ -75,6 +77,8 @@ func TestStreamWriter(t *testing.T) {
|
|||
assert.NoError(t, streamWriter.SetRow("A7", nil, RowOpts{Height: 20, Hidden: true, StyleID: styleID}))
|
||||
assert.Equal(t, ErrMaxRowHeight, streamWriter.SetRow("A8", nil, RowOpts{Height: MaxRowHeight + 1}))
|
||||
|
||||
assert.NoError(t, streamWriter.SetRow("A9", []interface{}{math.NaN(), math.Inf(0), math.Inf(-1)}))
|
||||
|
||||
for rowID := 10; rowID <= 51200; rowID++ {
|
||||
row := make([]interface{}, 50)
|
||||
for colID := 0; colID < 50; colID++ {
|
||||
|
@ -144,7 +148,7 @@ func TestStreamWriter(t *testing.T) {
|
|||
cells += len(row)
|
||||
}
|
||||
assert.NoError(t, rows.Close())
|
||||
assert.Equal(t, 2559559, cells)
|
||||
assert.Equal(t, 2559562, cells)
|
||||
// Save spreadsheet with password.
|
||||
assert.NoError(t, file.SaveAs(filepath.Join("test", "EncryptionTestStreamWriter.xlsx"), Options{Password: "password"}))
|
||||
assert.NoError(t, file.Close())
|
||||
|
@ -224,6 +228,8 @@ func TestStreamTable(t *testing.T) {
|
|||
assert.Equal(t, newCellNameToCoordinatesError("B", newInvalidCellNameError("B")), streamWriter.AddTable(&Table{Range: "A1:B"}))
|
||||
// Test add table with invalid table name
|
||||
assert.Equal(t, newInvalidNameError("1Table"), streamWriter.AddTable(&Table{Range: "A:B1", Name: "1Table"}))
|
||||
// Test add table with row number exceeds maximum limit
|
||||
assert.Equal(t, ErrMaxRows, streamWriter.AddTable(&Table{Range: "A1048576:C1048576"}))
|
||||
// Test add table with unsupported charset content types
|
||||
file.ContentTypes = nil
|
||||
file.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
|
||||
|
@ -332,8 +338,7 @@ func TestStreamSetRowWithStyle(t *testing.T) {
|
|||
Cell{StyleID: blueStyleID, Value: "value3"},
|
||||
&Cell{StyleID: blueStyleID, Value: "value3"},
|
||||
}, RowOpts{StyleID: grayStyleID}))
|
||||
err = streamWriter.Flush()
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, streamWriter.Flush())
|
||||
|
||||
ws, err := file.workSheetReader("Sheet1")
|
||||
assert.NoError(t, err)
|
||||
|
@ -398,3 +403,54 @@ func TestStreamWriterOutlineLevel(t *testing.T) {
|
|||
}
|
||||
assert.NoError(t, file.Close())
|
||||
}
|
||||
|
||||
func TestStreamWriterReader(t *testing.T) {
|
||||
var (
|
||||
err error
|
||||
sw = StreamWriter{
|
||||
rawData: bufferedWriter{},
|
||||
}
|
||||
)
|
||||
sw.rawData.tmp, err = os.CreateTemp(os.TempDir(), "excelize-")
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, sw.rawData.tmp.Close())
|
||||
// Test reader stat a closed temp file
|
||||
_, err = sw.rawData.Reader()
|
||||
assert.Error(t, err)
|
||||
_, err = sw.getRowValues(1, 1, 1)
|
||||
assert.Error(t, err)
|
||||
os.Remove(sw.rawData.tmp.Name())
|
||||
|
||||
sw = StreamWriter{
|
||||
file: NewFile(),
|
||||
rawData: bufferedWriter{},
|
||||
}
|
||||
// Test getRowValues without expected row
|
||||
sw.rawData.buf.WriteString("<worksheet><row r=\"1\"><c r=\"B1\"></c></row><worksheet/>")
|
||||
_, err = sw.getRowValues(1, 1, 1)
|
||||
assert.NoError(t, err)
|
||||
sw.rawData.buf.Reset()
|
||||
// Test getRowValues with illegal cell reference
|
||||
sw.rawData.buf.WriteString("<worksheet><row r=\"1\"><c r=\"A\"></c></row><worksheet/>")
|
||||
_, err = sw.getRowValues(1, 1, 1)
|
||||
assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), err)
|
||||
sw.rawData.buf.Reset()
|
||||
// Test getRowValues with invalid c element characters
|
||||
sw.rawData.buf.WriteString("<worksheet><row r=\"1\"><c></row><worksheet/>")
|
||||
_, err = sw.getRowValues(1, 1, 1)
|
||||
assert.EqualError(t, err, "XML syntax error on line 1: element <c> closed by </row>")
|
||||
sw.rawData.buf.Reset()
|
||||
}
|
||||
|
||||
func TestStreamWriterGetRowElement(t *testing.T) {
|
||||
// Test get row element without r attribute
|
||||
dec := xml.NewDecoder(strings.NewReader("<row ht=\"0\" />"))
|
||||
for {
|
||||
token, err := dec.Token()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
_, ok := getRowElement(token, 0)
|
||||
assert.False(t, ok)
|
||||
}
|
||||
}
|
||||
|
|
168
styles.go
168
styles.go
|
@ -1136,7 +1136,7 @@ var (
|
|||
},
|
||||
}
|
||||
|
||||
// extractStyleCondFuncs provides a function set to returns if shoudle be
|
||||
// extractStyleCondFuncs provides a function set to returns if should be
|
||||
// extract style definition by given style.
|
||||
extractStyleCondFuncs = map[string]func(xlsxXf, *xlsxStyleSheet) bool{
|
||||
"fill": func(xf xlsxXf, s *xlsxStyleSheet) bool {
|
||||
|
@ -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
|
||||
|
@ -2042,11 +2051,12 @@ func newFills(style *Style, fg bool) *xlsxFill {
|
|||
if style.Fill.Pattern > 18 || style.Fill.Pattern < 0 {
|
||||
break
|
||||
}
|
||||
if len(style.Fill.Color) < 1 {
|
||||
break
|
||||
}
|
||||
var pattern xlsxPatternFill
|
||||
pattern.PatternType = styleFillPatterns[style.Fill.Pattern]
|
||||
if len(style.Fill.Color) < 1 {
|
||||
fill.PatternFill = &pattern
|
||||
break
|
||||
}
|
||||
if fg {
|
||||
if pattern.FgColor == nil {
|
||||
pattern.FgColor = new(xlsxColor)
|
||||
|
@ -2186,19 +2196,22 @@ func setCellXfs(style *xlsxStyleSheet, fontID, numFmtID, fillID, borderID int, a
|
|||
}
|
||||
|
||||
// GetCellStyle provides a function to get cell style index by given worksheet
|
||||
// name and cell reference.
|
||||
// name and cell reference. This function is concurrency safe.
|
||||
func (f *File) GetCellStyle(sheet, cell string) (int, error) {
|
||||
f.mu.Lock()
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
if err != nil {
|
||||
f.mu.Unlock()
|
||||
return 0, err
|
||||
}
|
||||
f.mu.Unlock()
|
||||
ws.mu.Lock()
|
||||
defer ws.mu.Unlock()
|
||||
col, row, err := CellNameToCoordinates(cell)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
ws.prepareSheetXML(col, row)
|
||||
ws.mu.Lock()
|
||||
defer ws.mu.Unlock()
|
||||
return ws.prepareCellStyle(col, row, ws.SheetData.Row[row-1].C[col-1].S), err
|
||||
}
|
||||
|
||||
|
@ -2445,7 +2458,7 @@ func (f *File) SetCellStyle(sheet, topLeftCell, bottomRightCell string, styleID
|
|||
// {
|
||||
// Type: "cell",
|
||||
// Criteria: ">",
|
||||
// Format: format,
|
||||
// Format: &format,
|
||||
// Value: "6",
|
||||
// },
|
||||
// },
|
||||
|
@ -2458,7 +2471,7 @@ func (f *File) SetCellStyle(sheet, topLeftCell, bottomRightCell string, styleID
|
|||
// {
|
||||
// Type: "cell",
|
||||
// Criteria: ">",
|
||||
// Format: format,
|
||||
// Format: &format,
|
||||
// Value: "$C$1",
|
||||
// },
|
||||
// },
|
||||
|
@ -2482,7 +2495,7 @@ func (f *File) SetCellStyle(sheet, topLeftCell, bottomRightCell string, styleID
|
|||
// }
|
||||
// err = f.SetConditionalFormat("Sheet1", "D1:D10",
|
||||
// []excelize.ConditionalFormatOptions{
|
||||
// {Type: "cell", Criteria: ">", Format: format, Value: "6"},
|
||||
// {Type: "cell", Criteria: ">", Format: &format, Value: "6"},
|
||||
// },
|
||||
// )
|
||||
//
|
||||
|
@ -2534,7 +2547,7 @@ func (f *File) SetCellStyle(sheet, topLeftCell, bottomRightCell string, styleID
|
|||
// {
|
||||
// Type: "cell",
|
||||
// Criteria: "between",
|
||||
// Format: format,
|
||||
// Format: &format,
|
||||
// MinValue: 6",
|
||||
// MaxValue: 8",
|
||||
// },
|
||||
|
@ -2554,7 +2567,7 @@ func (f *File) SetCellStyle(sheet, topLeftCell, bottomRightCell string, styleID
|
|||
// {
|
||||
// Type: "average",
|
||||
// Criteria: "=",
|
||||
// Format: format1,
|
||||
// Format: &format1,
|
||||
// AboveAverage: true,
|
||||
// },
|
||||
// },
|
||||
|
@ -2566,7 +2579,7 @@ func (f *File) SetCellStyle(sheet, topLeftCell, bottomRightCell string, styleID
|
|||
// {
|
||||
// Type: "average",
|
||||
// Criteria: "=",
|
||||
// Format: format2,
|
||||
// Format: &format2,
|
||||
// AboveAverage: false,
|
||||
// },
|
||||
// },
|
||||
|
@ -2578,7 +2591,7 @@ func (f *File) SetCellStyle(sheet, topLeftCell, bottomRightCell string, styleID
|
|||
// // Highlight cells rules: Duplicate Values...
|
||||
// err := f.SetConditionalFormat("Sheet1", "A1:A10",
|
||||
// []excelize.ConditionalFormatOptions{
|
||||
// {Type: "duplicate", Criteria: "=", Format: format},
|
||||
// {Type: "duplicate", Criteria: "=", Format: &format},
|
||||
// },
|
||||
// )
|
||||
//
|
||||
|
@ -2587,7 +2600,7 @@ func (f *File) SetCellStyle(sheet, topLeftCell, bottomRightCell string, styleID
|
|||
// // Highlight cells rules: Not Equal To...
|
||||
// err := f.SetConditionalFormat("Sheet1", "A1:A10",
|
||||
// []excelize.ConditionalFormatOptions{
|
||||
// {Type: "unique", Criteria: "=", Format: format},
|
||||
// {Type: "unique", Criteria: "=", Format: &format},
|
||||
// },
|
||||
// )
|
||||
//
|
||||
|
@ -2600,7 +2613,7 @@ func (f *File) SetCellStyle(sheet, topLeftCell, bottomRightCell string, styleID
|
|||
// {
|
||||
// Type: "top",
|
||||
// Criteria: "=",
|
||||
// Format: format,
|
||||
// Format: &format,
|
||||
// Value: "6",
|
||||
// },
|
||||
// },
|
||||
|
@ -2613,7 +2626,7 @@ func (f *File) SetCellStyle(sheet, topLeftCell, bottomRightCell string, styleID
|
|||
// {
|
||||
// Type: "top",
|
||||
// Criteria: "=",
|
||||
// Format: format,
|
||||
// Format: &format,
|
||||
// Value: "6",
|
||||
// Percent: true,
|
||||
// },
|
||||
|
@ -2931,10 +2944,7 @@ func (f *File) appendCfRule(ws *xlsxWorksheet, rule *xlsxX14CfRule) error {
|
|||
// settings for cell value (include between, not between, equal, not equal,
|
||||
// greater than and less than) by given conditional formatting rule.
|
||||
func (f *File) extractCondFmtCellIs(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
|
||||
format := ConditionalFormatOptions{StopIfTrue: c.StopIfTrue, Type: "cell", Criteria: operatorType[c.Operator]}
|
||||
if c.DxfID != nil {
|
||||
format.Format = *c.DxfID
|
||||
}
|
||||
format := ConditionalFormatOptions{Format: c.DxfID, StopIfTrue: c.StopIfTrue, Type: "cell", Criteria: operatorType[c.Operator]}
|
||||
if len(c.Formula) == 2 {
|
||||
format.MinValue, format.MaxValue = c.Formula[0], c.Formula[1]
|
||||
return format
|
||||
|
@ -2946,21 +2956,13 @@ func (f *File) extractCondFmtCellIs(c *xlsxCfRule, extLst *xlsxExtLst) Condition
|
|||
// extractCondFmtTimePeriod provides a function to extract conditional format
|
||||
// settings for time period by given conditional formatting rule.
|
||||
func (f *File) extractCondFmtTimePeriod(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
|
||||
format := ConditionalFormatOptions{StopIfTrue: c.StopIfTrue, Type: "time_period", Criteria: operatorType[c.Operator]}
|
||||
if c.DxfID != nil {
|
||||
format.Format = *c.DxfID
|
||||
}
|
||||
return format
|
||||
return ConditionalFormatOptions{Format: c.DxfID, StopIfTrue: c.StopIfTrue, Type: "time_period", Criteria: operatorType[c.Operator]}
|
||||
}
|
||||
|
||||
// extractCondFmtText provides a function to extract conditional format
|
||||
// settings for text cell values by given conditional formatting rule.
|
||||
func (f *File) extractCondFmtText(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
|
||||
format := ConditionalFormatOptions{StopIfTrue: c.StopIfTrue, Type: "text", Criteria: operatorType[c.Operator], Value: c.Text}
|
||||
if c.DxfID != nil {
|
||||
format.Format = *c.DxfID
|
||||
}
|
||||
return format
|
||||
return ConditionalFormatOptions{Format: c.DxfID, StopIfTrue: c.StopIfTrue, Type: "text", Criteria: operatorType[c.Operator], Value: c.Text}
|
||||
}
|
||||
|
||||
// extractCondFmtTop10 provides a function to extract conditional format
|
||||
|
@ -2968,15 +2970,13 @@ func (f *File) extractCondFmtText(c *xlsxCfRule, extLst *xlsxExtLst) Conditional
|
|||
// rule.
|
||||
func (f *File) extractCondFmtTop10(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
|
||||
format := ConditionalFormatOptions{
|
||||
Format: c.DxfID,
|
||||
StopIfTrue: c.StopIfTrue,
|
||||
Type: "top",
|
||||
Criteria: "=",
|
||||
Percent: c.Percent,
|
||||
Value: strconv.Itoa(c.Rank),
|
||||
}
|
||||
if c.DxfID != nil {
|
||||
format.Format = *c.DxfID
|
||||
}
|
||||
if c.Bottom {
|
||||
format.Type = "bottom"
|
||||
}
|
||||
|
@ -2988,13 +2988,11 @@ func (f *File) extractCondFmtTop10(c *xlsxCfRule, extLst *xlsxExtLst) Conditiona
|
|||
// rule.
|
||||
func (f *File) extractCondFmtAboveAverage(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
|
||||
format := ConditionalFormatOptions{
|
||||
Format: c.DxfID,
|
||||
StopIfTrue: c.StopIfTrue,
|
||||
Type: "average",
|
||||
Criteria: "=",
|
||||
}
|
||||
if c.DxfID != nil {
|
||||
format.Format = *c.DxfID
|
||||
}
|
||||
if c.AboveAverage != nil {
|
||||
format.AboveAverage = *c.AboveAverage
|
||||
}
|
||||
|
@ -3005,7 +3003,8 @@ func (f *File) extractCondFmtAboveAverage(c *xlsxCfRule, extLst *xlsxExtLst) Con
|
|||
// conditional format settings for duplicate and unique values by given
|
||||
// conditional formatting rule.
|
||||
func (f *File) extractCondFmtDuplicateUniqueValues(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
|
||||
format := ConditionalFormatOptions{
|
||||
return ConditionalFormatOptions{
|
||||
Format: c.DxfID,
|
||||
StopIfTrue: c.StopIfTrue,
|
||||
Type: map[string]string{
|
||||
"duplicateValues": "duplicate",
|
||||
|
@ -3013,62 +3012,46 @@ func (f *File) extractCondFmtDuplicateUniqueValues(c *xlsxCfRule, extLst *xlsxEx
|
|||
}[c.Type],
|
||||
Criteria: "=",
|
||||
}
|
||||
if c.DxfID != nil {
|
||||
format.Format = *c.DxfID
|
||||
}
|
||||
return format
|
||||
}
|
||||
|
||||
// extractCondFmtBlanks provides a function to extract conditional format
|
||||
// settings for blank cells by given conditional formatting rule.
|
||||
func (f *File) extractCondFmtBlanks(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
|
||||
format := ConditionalFormatOptions{
|
||||
return ConditionalFormatOptions{
|
||||
Format: c.DxfID,
|
||||
StopIfTrue: c.StopIfTrue,
|
||||
Type: "blanks",
|
||||
}
|
||||
if c.DxfID != nil {
|
||||
format.Format = *c.DxfID
|
||||
}
|
||||
return format
|
||||
}
|
||||
|
||||
// extractCondFmtNoBlanks provides a function to extract conditional format
|
||||
// settings for no blank cells by given conditional formatting rule.
|
||||
func (f *File) extractCondFmtNoBlanks(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
|
||||
format := ConditionalFormatOptions{
|
||||
return ConditionalFormatOptions{
|
||||
Format: c.DxfID,
|
||||
StopIfTrue: c.StopIfTrue,
|
||||
Type: "no_blanks",
|
||||
}
|
||||
if c.DxfID != nil {
|
||||
format.Format = *c.DxfID
|
||||
}
|
||||
return format
|
||||
}
|
||||
|
||||
// extractCondFmtErrors provides a function to extract conditional format
|
||||
// settings for cells with errors by given conditional formatting rule.
|
||||
func (f *File) extractCondFmtErrors(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
|
||||
format := ConditionalFormatOptions{
|
||||
return ConditionalFormatOptions{
|
||||
Format: c.DxfID,
|
||||
StopIfTrue: c.StopIfTrue,
|
||||
Type: "errors",
|
||||
}
|
||||
if c.DxfID != nil {
|
||||
format.Format = *c.DxfID
|
||||
}
|
||||
return format
|
||||
}
|
||||
|
||||
// extractCondFmtNoErrors provides a function to extract conditional format
|
||||
// settings for cells without errors by given conditional formatting rule.
|
||||
func (f *File) extractCondFmtNoErrors(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
|
||||
format := ConditionalFormatOptions{
|
||||
return ConditionalFormatOptions{
|
||||
Format: c.DxfID,
|
||||
StopIfTrue: c.StopIfTrue,
|
||||
Type: "no_errors",
|
||||
}
|
||||
if c.DxfID != nil {
|
||||
format.Format = *c.DxfID
|
||||
}
|
||||
return format
|
||||
}
|
||||
|
||||
// extractCondFmtColorScale provides a function to extract conditional format
|
||||
|
@ -3165,10 +3148,7 @@ func (f *File) extractCondFmtDataBar(c *xlsxCfRule, extLst *xlsxExtLst) Conditio
|
|||
// extractCondFmtExp provides a function to extract conditional format settings
|
||||
// for expression by given conditional formatting rule.
|
||||
func (f *File) extractCondFmtExp(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
|
||||
format := ConditionalFormatOptions{StopIfTrue: c.StopIfTrue, Type: "formula"}
|
||||
if c.DxfID != nil {
|
||||
format.Format = *c.DxfID
|
||||
}
|
||||
format := ConditionalFormatOptions{Format: c.DxfID, StopIfTrue: c.StopIfTrue, Type: "formula"}
|
||||
if len(c.Formula) > 0 {
|
||||
format.Criteria = c.Formula[0]
|
||||
}
|
||||
|
@ -3234,7 +3214,7 @@ func drawCondFmtCellIs(p int, ct, ref, GUID string, format *ConditionalFormatOpt
|
|||
StopIfTrue: format.StopIfTrue,
|
||||
Type: validType[format.Type],
|
||||
Operator: ct,
|
||||
DxfID: intPtr(format.Format),
|
||||
DxfID: format.Format,
|
||||
}
|
||||
// "between" and "not between" criteria require 2 values.
|
||||
if ct == "between" || ct == "notBetween" {
|
||||
|
@ -3268,7 +3248,7 @@ func drawCondFmtTimePeriod(p int, ct, ref, GUID string, format *ConditionalForma
|
|||
"continue month": fmt.Sprintf("AND(MONTH(%[1]s)=MONTH(TODAY())+1,OR(YEAR(%[1]s)=YEAR(TODAY()),AND(MONTH(%[1]s)=12,YEAR(%[1]s)=YEAR(TODAY())+1)))", ref),
|
||||
}[ct],
|
||||
},
|
||||
DxfID: intPtr(format.Format),
|
||||
DxfID: format.Format,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -3298,7 +3278,7 @@ func drawCondFmtText(p int, ct, ref, GUID string, format *ConditionalFormatOptio
|
|||
strings.NewReplacer(`"`, `""`).Replace(format.Value), ref),
|
||||
}[ct],
|
||||
},
|
||||
DxfID: intPtr(format.Format),
|
||||
DxfID: format.Format,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -3312,7 +3292,7 @@ func drawCondFmtTop10(p int, ct, ref, GUID string, format *ConditionalFormatOpti
|
|||
Bottom: format.Type == "bottom",
|
||||
Type: validType[format.Type],
|
||||
Rank: 10,
|
||||
DxfID: intPtr(format.Format),
|
||||
DxfID: format.Format,
|
||||
Percent: format.Percent,
|
||||
}
|
||||
if rank, err := strconv.Atoi(format.Value); err == nil {
|
||||
|
@ -3330,7 +3310,7 @@ func drawCondFmtAboveAverage(p int, ct, ref, GUID string, format *ConditionalFor
|
|||
StopIfTrue: format.StopIfTrue,
|
||||
Type: validType[format.Type],
|
||||
AboveAverage: boolPtr(format.AboveAverage),
|
||||
DxfID: intPtr(format.Format),
|
||||
DxfID: format.Format,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -3342,7 +3322,7 @@ func drawCondFmtDuplicateUniqueValues(p int, ct, ref, GUID string, format *Condi
|
|||
Priority: p + 1,
|
||||
StopIfTrue: format.StopIfTrue,
|
||||
Type: validType[format.Type],
|
||||
DxfID: intPtr(format.Format),
|
||||
DxfID: format.Format,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -3430,7 +3410,7 @@ func drawCondFmtExp(p int, ct, ref, GUID string, format *ConditionalFormatOption
|
|||
StopIfTrue: format.StopIfTrue,
|
||||
Type: validType[format.Type],
|
||||
Formula: []string{format.Criteria},
|
||||
DxfID: intPtr(format.Format),
|
||||
DxfID: format.Format,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -3442,7 +3422,7 @@ func drawCondFmtErrors(p int, ct, ref, GUID string, format *ConditionalFormatOpt
|
|||
StopIfTrue: format.StopIfTrue,
|
||||
Type: validType[format.Type],
|
||||
Formula: []string{fmt.Sprintf("ISERROR(%s)", ref)},
|
||||
DxfID: intPtr(format.Format),
|
||||
DxfID: format.Format,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -3454,7 +3434,7 @@ func drawCondFmtNoErrors(p int, ct, ref, GUID string, format *ConditionalFormatO
|
|||
StopIfTrue: format.StopIfTrue,
|
||||
Type: validType[format.Type],
|
||||
Formula: []string{fmt.Sprintf("NOT(ISERROR(%s))", ref)},
|
||||
DxfID: intPtr(format.Format),
|
||||
DxfID: format.Format,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -3466,7 +3446,7 @@ func drawCondFmtBlanks(p int, ct, ref, GUID string, format *ConditionalFormatOpt
|
|||
StopIfTrue: format.StopIfTrue,
|
||||
Type: validType[format.Type],
|
||||
Formula: []string{fmt.Sprintf("LEN(TRIM(%s))=0", ref)},
|
||||
DxfID: intPtr(format.Format),
|
||||
DxfID: format.Format,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -3478,7 +3458,7 @@ func drawCondFmtNoBlanks(p int, ct, ref, GUID string, format *ConditionalFormatO
|
|||
StopIfTrue: format.StopIfTrue,
|
||||
Type: validType[format.Type],
|
||||
Formula: []string{fmt.Sprintf("LEN(TRIM(%s))>0", ref)},
|
||||
DxfID: intPtr(format.Format),
|
||||
DxfID: format.Format,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -172,7 +172,7 @@ func TestSetConditionalFormat(t *testing.T) {
|
|||
// Test creating a conditional format with a solid color data bar style
|
||||
f := NewFile()
|
||||
condFmts := []ConditionalFormatOptions{
|
||||
{Type: "data_bar", BarColor: "#A9D08E", BarSolid: true, Format: 0, Criteria: "=", MinType: "min", MaxType: "max"},
|
||||
{Type: "data_bar", BarColor: "#A9D08E", BarSolid: true, Format: intPtr(0), Criteria: "=", MinType: "min", MaxType: "max"},
|
||||
}
|
||||
for _, ref := range []string{"A1:A2", "B1:B2"} {
|
||||
assert.NoError(t, f.SetConditionalFormat("Sheet1", ref, condFmts))
|
||||
|
@ -243,36 +243,36 @@ func TestSetConditionalFormat(t *testing.T) {
|
|||
|
||||
func TestGetConditionalFormats(t *testing.T) {
|
||||
for _, format := range [][]ConditionalFormatOptions{
|
||||
{{Type: "cell", Format: 1, Criteria: "greater than", Value: "6"}},
|
||||
{{Type: "cell", Format: 1, Criteria: "between", MinValue: "6", MaxValue: "8"}},
|
||||
{{Type: "time_period", Format: 1, Criteria: "yesterday"}},
|
||||
{{Type: "time_period", Format: 1, Criteria: "today"}},
|
||||
{{Type: "time_period", Format: 1, Criteria: "tomorrow"}},
|
||||
{{Type: "time_period", Format: 1, Criteria: "last 7 days"}},
|
||||
{{Type: "time_period", Format: 1, Criteria: "last week"}},
|
||||
{{Type: "time_period", Format: 1, Criteria: "this week"}},
|
||||
{{Type: "time_period", Format: 1, Criteria: "continue week"}},
|
||||
{{Type: "time_period", Format: 1, Criteria: "last month"}},
|
||||
{{Type: "time_period", Format: 1, Criteria: "this month"}},
|
||||
{{Type: "time_period", Format: 1, Criteria: "continue month"}},
|
||||
{{Type: "text", Format: 1, Criteria: "containing", Value: "~!@#$%^&*()_+{}|:<>?\"';"}},
|
||||
{{Type: "text", Format: 1, Criteria: "not containing", Value: "text"}},
|
||||
{{Type: "text", Format: 1, Criteria: "begins with", Value: "prefix"}},
|
||||
{{Type: "text", Format: 1, Criteria: "ends with", Value: "suffix"}},
|
||||
{{Type: "top", Format: 1, Criteria: "=", Value: "6"}},
|
||||
{{Type: "bottom", Format: 1, Criteria: "=", Value: "6"}},
|
||||
{{Type: "average", AboveAverage: true, Format: 1, Criteria: "="}},
|
||||
{{Type: "duplicate", Format: 1, Criteria: "="}},
|
||||
{{Type: "unique", Format: 1, Criteria: "="}},
|
||||
{{Type: "cell", Format: intPtr(1), Criteria: "greater than", Value: "6"}},
|
||||
{{Type: "cell", Format: intPtr(1), Criteria: "between", MinValue: "6", MaxValue: "8"}},
|
||||
{{Type: "time_period", Format: intPtr(1), Criteria: "yesterday"}},
|
||||
{{Type: "time_period", Format: intPtr(1), Criteria: "today"}},
|
||||
{{Type: "time_period", Format: intPtr(1), Criteria: "tomorrow"}},
|
||||
{{Type: "time_period", Format: intPtr(1), Criteria: "last 7 days"}},
|
||||
{{Type: "time_period", Format: intPtr(1), Criteria: "last week"}},
|
||||
{{Type: "time_period", Format: intPtr(1), Criteria: "this week"}},
|
||||
{{Type: "time_period", Format: intPtr(1), Criteria: "continue week"}},
|
||||
{{Type: "time_period", Format: intPtr(1), Criteria: "last month"}},
|
||||
{{Type: "time_period", Format: intPtr(1), Criteria: "this month"}},
|
||||
{{Type: "time_period", Format: intPtr(1), Criteria: "continue month"}},
|
||||
{{Type: "text", Format: intPtr(1), Criteria: "containing", Value: "~!@#$%^&*()_+{}|:<>?\"';"}},
|
||||
{{Type: "text", Format: intPtr(1), Criteria: "not containing", Value: "text"}},
|
||||
{{Type: "text", Format: intPtr(1), Criteria: "begins with", Value: "prefix"}},
|
||||
{{Type: "text", Format: intPtr(1), Criteria: "ends with", Value: "suffix"}},
|
||||
{{Type: "top", Format: intPtr(1), Criteria: "=", Value: "6"}},
|
||||
{{Type: "bottom", Format: intPtr(1), Criteria: "=", Value: "6"}},
|
||||
{{Type: "average", AboveAverage: true, Format: intPtr(1), Criteria: "="}},
|
||||
{{Type: "duplicate", Format: intPtr(1), Criteria: "="}},
|
||||
{{Type: "unique", Format: intPtr(1), Criteria: "="}},
|
||||
{{Type: "3_color_scale", Criteria: "=", MinType: "num", MidType: "num", MaxType: "num", MinValue: "-10", MidValue: "50", MaxValue: "10", MinColor: "#FF0000", MidColor: "#00FF00", MaxColor: "#0000FF"}},
|
||||
{{Type: "2_color_scale", Criteria: "=", MinType: "num", MaxType: "num", MinColor: "#FF0000", MaxColor: "#0000FF"}},
|
||||
{{Type: "data_bar", Criteria: "=", MinType: "num", MaxType: "num", MinValue: "-10", MaxValue: "10", BarBorderColor: "#0000FF", BarColor: "#638EC6", BarOnly: true, BarSolid: true, StopIfTrue: true}},
|
||||
{{Type: "data_bar", Criteria: "=", MinType: "min", MaxType: "max", BarBorderColor: "#0000FF", BarColor: "#638EC6", BarDirection: "rightToLeft", BarOnly: true, BarSolid: true, StopIfTrue: true}},
|
||||
{{Type: "formula", Format: 1, Criteria: "="}},
|
||||
{{Type: "blanks", Format: 1}},
|
||||
{{Type: "no_blanks", Format: 1}},
|
||||
{{Type: "errors", Format: 1}},
|
||||
{{Type: "no_errors", Format: 1}},
|
||||
{{Type: "formula", Format: intPtr(1), Criteria: "="}},
|
||||
{{Type: "blanks", Format: intPtr(1)}},
|
||||
{{Type: "no_blanks", Format: intPtr(1)}},
|
||||
{{Type: "errors", Format: intPtr(1)}},
|
||||
{{Type: "no_errors", Format: intPtr(1)}},
|
||||
{{Type: "icon_set", IconStyle: "3Arrows", ReverseIcons: true, IconsOnly: true}},
|
||||
} {
|
||||
f := NewFile()
|
||||
|
@ -309,7 +309,7 @@ func TestUnsetConditionalFormat(t *testing.T) {
|
|||
assert.NoError(t, f.UnsetConditionalFormat("Sheet1", "A1:A10"))
|
||||
format, err := f.NewConditionalStyle(&Style{Font: &Font{Color: "9A0511"}, Fill: Fill{Type: "pattern", Color: []string{"FEC7CE"}, Pattern: 1}})
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, f.SetConditionalFormat("Sheet1", "A1:A10", []ConditionalFormatOptions{{Type: "cell", Criteria: ">", Format: format, Value: "6"}}))
|
||||
assert.NoError(t, f.SetConditionalFormat("Sheet1", "A1:A10", []ConditionalFormatOptions{{Type: "cell", Criteria: ">", Format: &format, Value: "6"}}))
|
||||
assert.NoError(t, f.UnsetConditionalFormat("Sheet1", "A1:A10"))
|
||||
// Test unset conditional format on not exists worksheet
|
||||
assert.EqualError(t, f.UnsetConditionalFormat("SheetN", "A1:A10"), "sheet SheetN does not exist")
|
||||
|
@ -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) {
|
||||
|
|
32
table.go
32
table.go
|
@ -173,11 +173,11 @@ func (f *File) DeleteTable(name string) error {
|
|||
if err := checkDefinedName(name); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, sheet := range f.GetSheetList() {
|
||||
tables, err := f.GetTables(sheet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tbls, err := f.getTables()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for sheet, tables := range tbls {
|
||||
for _, table := range tables {
|
||||
if table.Name != name {
|
||||
continue
|
||||
|
@ -201,6 +201,20 @@ func (f *File) DeleteTable(name string) error {
|
|||
return newNoExistTableError(name)
|
||||
}
|
||||
|
||||
// getTables provides a function to get all tables in a workbook.
|
||||
func (f *File) getTables() (map[string][]Table, error) {
|
||||
tables := map[string][]Table{}
|
||||
for _, sheetName := range f.GetSheetList() {
|
||||
tbls, err := f.GetTables(sheetName)
|
||||
e := ErrSheetNotExist{sheetName}
|
||||
if err != nil && err.Error() != newNotWorksheetError(sheetName).Error() && err.Error() != e.Error() {
|
||||
return tables, err
|
||||
}
|
||||
tables[sheetName] = append(tables[sheetName], tbls...)
|
||||
}
|
||||
return tables, nil
|
||||
}
|
||||
|
||||
// countTables provides a function to get table files count storage in the
|
||||
// folder xl/tables.
|
||||
func (f *File) countTables() int {
|
||||
|
@ -350,7 +364,7 @@ func (f *File) addTable(sheet, tableXML string, x1, y1, x2, y2, i int, opts *Tab
|
|||
y1++
|
||||
}
|
||||
// Correct table range reference, such correct C1:B3 to B1:C3.
|
||||
ref, err := f.coordinatesToRangeRef([]int{x1, y1, x2, y2})
|
||||
ref, err := coordinatesToRangeRef([]int{x1, y1, x2, y2})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -463,7 +477,7 @@ func (f *File) AutoFilter(sheet, rangeRef string, opts []AutoFilterOptions) erro
|
|||
}
|
||||
_ = sortCoordinates(coordinates)
|
||||
// Correct reference range, such correct C1:B3 to B1:C3.
|
||||
ref, _ := f.coordinatesToRangeRef(coordinates, true)
|
||||
ref, _ := coordinatesToRangeRef(coordinates, true)
|
||||
wb, err := f.workbookReader()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -474,7 +488,7 @@ func (f *File) AutoFilter(sheet, rangeRef string, opts []AutoFilterOptions) erro
|
|||
}
|
||||
filterRange := fmt.Sprintf("'%s'!%s", sheet, ref)
|
||||
d := xlsxDefinedName{
|
||||
Name: builtInDefinedNames[2],
|
||||
Name: builtInDefinedNames[3],
|
||||
Hidden: true,
|
||||
LocalSheetID: intPtr(sheetID),
|
||||
Data: filterRange,
|
||||
|
@ -490,7 +504,7 @@ func (f *File) AutoFilter(sheet, rangeRef string, opts []AutoFilterOptions) erro
|
|||
if definedName.LocalSheetID != nil {
|
||||
localSheetID = *definedName.LocalSheetID
|
||||
}
|
||||
if definedName.Name == builtInDefinedNames[2] && localSheetID == sheetID && definedName.Hidden {
|
||||
if definedName.Name == builtInDefinedNames[3] && localSheetID == sheetID && definedName.Hidden {
|
||||
wb.DefinedNames.DefinedName[idx].Data = filterRange
|
||||
definedNameExists = true
|
||||
}
|
||||
|
|
|
@ -174,7 +174,7 @@ func TestAutoFilter(t *testing.T) {
|
|||
assert.EqualError(t, f.AutoFilter("Sheet1", "D4:B1", nil), "XML syntax error on line 1: invalid UTF-8")
|
||||
// Test add auto filter with empty local sheet ID
|
||||
f = NewFile()
|
||||
f.WorkBook = &xlsxWorkbook{DefinedNames: &xlsxDefinedNames{DefinedName: []xlsxDefinedName{{Name: builtInDefinedNames[2], Hidden: true}}}}
|
||||
f.WorkBook = &xlsxWorkbook{DefinedNames: &xlsxDefinedNames{DefinedName: []xlsxDefinedName{{Name: builtInDefinedNames[3], Hidden: true}}}}
|
||||
assert.NoError(t, f.AutoFilter("Sheet1", "A1:B1", nil))
|
||||
}
|
||||
|
||||
|
|
48
templates.go
48
templates.go
|
@ -102,8 +102,9 @@ const (
|
|||
ExtURICalcFeatures = "{B58B0392-4F1F-4190-BB64-5DF3571DCE5F}"
|
||||
ExtURIConditionalFormattingRuleID = "{B025F937-C7B1-47D3-B67F-A62EFF666E3E}"
|
||||
ExtURIConditionalFormattings = "{78C0D931-6437-407d-A8EE-F0AAD7539E65}"
|
||||
ExtURIDataField = "{E15A36E0-9728-4E99-A89B-3F7291B0FE68}"
|
||||
ExtURIDataModel = "{FCE2AD5D-F65C-4FA6-A056-5C36A1767C68}"
|
||||
ExtURIDataValidations = "{CCE6A557-97BC-4B89-ADB6-D9C93CAAB3DF}"
|
||||
ExtURIDataValidations = "{CCE6A557-97BC-4b89-ADB6-D9C93CAAB3DF}"
|
||||
ExtURIDrawingBlip = "{28A0092B-C50C-407E-A947-70E740481C1C}"
|
||||
ExtURIExternalLinkPr = "{FCE6A71B-6B00-49CD-AB44-F6B1AE7CDE65}"
|
||||
ExtURIIgnoredErrors = "{01252117-D84E-4E92-8308-4BE1C098FCBB}"
|
||||
|
@ -112,6 +113,9 @@ const (
|
|||
ExtURIPivotCacheDefinition = "{725AE2AE-9491-48be-B2B4-4EB974FC3084}"
|
||||
ExtURIPivotCachesX14 = "{876F7934-8845-4945-9796-88D515C7AA90}"
|
||||
ExtURIPivotCachesX15 = "{841E416B-1EF1-43b6-AB56-02D37102CBD5}"
|
||||
ExtURIPivotField = "{2946ED86-A175-432a-8AC1-64E0C546D7DE}"
|
||||
ExtURIPivotFilter = "{0605FD5F-26C8-4aeb-8148-2DB25E43C511}"
|
||||
ExtURIPivotHierarchy = "{F1805F06-0CD304483-9156-8803C3D141DF}"
|
||||
ExtURIPivotTableReferences = "{983426D0-5260-488c-9760-48F4B6AC55F4}"
|
||||
ExtURIProtectedRanges = "{FC87AEE6-9EDD-4A0A-B7FB-166176984837}"
|
||||
ExtURISlicerCacheDefinition = "{2F2917AC-EB37-4324-AD4E-5DD8C200BD13}"
|
||||
|
@ -266,19 +270,25 @@ var supportedChartDataLabelsPosition = map[ChartType][]ChartDataLabelPositionTyp
|
|||
}
|
||||
|
||||
const (
|
||||
defaultTempFileSST = "sharedStrings"
|
||||
defaultXMLPathCalcChain = "xl/calcChain.xml"
|
||||
defaultXMLPathCellImages = "xl/cellimages.xml"
|
||||
defaultXMLPathCellImagesRels = "xl/_rels/cellimages.xml.rels"
|
||||
defaultXMLPathContentTypes = "[Content_Types].xml"
|
||||
defaultXMLPathDocPropsApp = "docProps/app.xml"
|
||||
defaultXMLPathDocPropsCore = "docProps/core.xml"
|
||||
defaultXMLPathSharedStrings = "xl/sharedStrings.xml"
|
||||
defaultXMLPathStyles = "xl/styles.xml"
|
||||
defaultXMLPathTheme = "xl/theme/theme1.xml"
|
||||
defaultXMLPathVolatileDeps = "xl/volatileDependencies.xml"
|
||||
defaultXMLPathWorkbook = "xl/workbook.xml"
|
||||
defaultXMLPathWorkbookRels = "xl/_rels/workbook.xml.rels"
|
||||
defaultTempFileSST = "sharedStrings"
|
||||
defaultXMLMetadata = "xl/metadata.xml"
|
||||
defaultXMLPathCalcChain = "xl/calcChain.xml"
|
||||
defaultXMLPathCellImages = "xl/cellimages.xml"
|
||||
defaultXMLPathCellImagesRels = "xl/_rels/cellimages.xml.rels"
|
||||
defaultXMLPathContentTypes = "[Content_Types].xml"
|
||||
defaultXMLPathDocPropsApp = "docProps/app.xml"
|
||||
defaultXMLPathDocPropsCore = "docProps/core.xml"
|
||||
defaultXMLPathSharedStrings = "xl/sharedStrings.xml"
|
||||
defaultXMLPathStyles = "xl/styles.xml"
|
||||
defaultXMLPathTheme = "xl/theme/theme1.xml"
|
||||
defaultXMLPathVolatileDeps = "xl/volatileDependencies.xml"
|
||||
defaultXMLPathWorkbook = "xl/workbook.xml"
|
||||
defaultXMLPathWorkbookRels = "xl/_rels/workbook.xml.rels"
|
||||
defaultXMLRdRichValuePart = "xl/richData/rdrichvalue.xml"
|
||||
defaultXMLRdRichValueRel = "xl/richData/richValueRel.xml"
|
||||
defaultXMLRdRichValueRelRels = "xl/richData/_rels/richValueRel.xml.rels"
|
||||
defaultXMLRdRichValueWebImagePart = "xl/richData/rdRichValueWebImage.xml"
|
||||
defaultXMLRdRichValueWebImagePartRels = "xl/richData/_rels/rdRichValueWebImage.xml.rels"
|
||||
)
|
||||
|
||||
// IndexedColorMapping is the table of default mappings from indexed color value
|
||||
|
@ -485,9 +495,19 @@ var supportedDrawingUnderlineTypes = []string{
|
|||
"wavyDbl",
|
||||
}
|
||||
|
||||
// supportedDrawingTextVerticalType defined supported text vertical types in
|
||||
// drawing markup language.
|
||||
var supportedDrawingTextVerticalType = []string{"horz", "vert", "vert270", "wordArtVert", "eaVert", "mongolianVert", "wordArtVertRtl"}
|
||||
|
||||
// supportedPositioning defined supported positioning types.
|
||||
var supportedPositioning = []string{"absolute", "oneCell", "twoCell"}
|
||||
|
||||
// supportedPageOrientation defined supported page setup page orientation.
|
||||
var supportedPageOrientation = []string{"portrait", "landscape"}
|
||||
|
||||
// supportedPageOrder defined supported page setup page order.
|
||||
var supportedPageOrder = []string{"overThenDown", "downThenOver"}
|
||||
|
||||
// builtInDefinedNames defined built-in defined names are built with a _xlnm prefix.
|
||||
var builtInDefinedNames = []string{"_xlnm.Print_Area", "_xlnm.Print_Titles", "_xlnm.Criteria", "_xlnm._FilterDatabase", "_xlnm.Extract", "_xlnm.Consolidate_Area", "_xlnm.Database", "_xlnm.Sheet_Title"}
|
||||
|
||||
|
|
154
vml.go
154
vml.go
|
@ -36,6 +36,16 @@ const (
|
|||
FormControlScrollBar
|
||||
)
|
||||
|
||||
// HeaderFooterImagePositionType is the type of header and footer image position.
|
||||
type HeaderFooterImagePositionType byte
|
||||
|
||||
// Worksheet header and footer image position types enumeration.
|
||||
const (
|
||||
HeaderFooterImagePositionLeft HeaderFooterImagePositionType = iota
|
||||
HeaderFooterImagePositionCenter
|
||||
HeaderFooterImagePositionRight
|
||||
)
|
||||
|
||||
// GetComments retrieves all comments in a worksheet by given worksheet name.
|
||||
func (f *File) GetComments(sheet string) ([]Comment, error) {
|
||||
var comments []Comment
|
||||
|
@ -519,6 +529,7 @@ func (f *File) addVMLObject(opts vmlOptions) error {
|
|||
}
|
||||
vmlID = f.countVMLDrawing() + 1
|
||||
}
|
||||
sheetID := f.getSheetID(opts.sheet)
|
||||
drawingVML := "xl/drawings/vmlDrawing" + strconv.Itoa(vmlID) + ".vml"
|
||||
sheetRelationshipsDrawingVML := "../drawings/vmlDrawing" + strconv.Itoa(vmlID) + ".vml"
|
||||
sheetXMLPath, _ := f.getSheetXMLPath(opts.sheet)
|
||||
|
@ -534,7 +545,7 @@ func (f *File) addVMLObject(opts vmlOptions) error {
|
|||
f.addSheetNameSpace(opts.sheet, SourceRelationship)
|
||||
f.addSheetLegacyDrawing(opts.sheet, rID)
|
||||
}
|
||||
if err = f.addDrawingVML(vmlID, drawingVML, prepareFormCtrlOptions(&opts)); err != nil {
|
||||
if err = f.addDrawingVML(sheetID, drawingVML, prepareFormCtrlOptions(&opts)); err != nil {
|
||||
return err
|
||||
}
|
||||
if !opts.formCtrl {
|
||||
|
@ -823,7 +834,7 @@ func (f *File) addFormCtrlShape(preset formCtrlPreset, col, row int, anchor stri
|
|||
// anchor value is a comma-separated list of data written out as: LeftColumn,
|
||||
// LeftOffset, TopRow, TopOffset, RightColumn, RightOffset, BottomRow,
|
||||
// BottomOffset.
|
||||
func (f *File) addDrawingVML(dataID int, drawingVML string, opts *vmlOptions) error {
|
||||
func (f *File) addDrawingVML(sheetID int, drawingVML string, opts *vmlOptions) error {
|
||||
col, row, err := CellNameToCoordinates(opts.FormControl.Cell)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -843,7 +854,7 @@ func (f *File) addDrawingVML(dataID int, drawingVML string, opts *vmlOptions) er
|
|||
XMLNSx: "urn:schemas-microsoft-com:office:excel",
|
||||
XMLNSmv: "http://macVmlSchemaUri",
|
||||
ShapeLayout: &xlsxShapeLayout{
|
||||
Ext: "edit", IDmap: &xlsxIDmap{Ext: "edit", Data: dataID},
|
||||
Ext: "edit", IDmap: &xlsxIDmap{Ext: "edit", Data: sheetID},
|
||||
},
|
||||
ShapeType: &xlsxShapeType{
|
||||
ID: fmt.Sprintf("_x0000_t%d", vmlID),
|
||||
|
@ -1070,3 +1081,140 @@ func extractVMLFont(font []decodeVMLFont) []RichTextRun {
|
|||
}
|
||||
return runs
|
||||
}
|
||||
|
||||
// AddHeaderFooterImage provides a mechanism to set the graphics that can be
|
||||
// referenced in the header and footer definitions via &G, supported image
|
||||
// types: EMF, EMZ, GIF, JPEG, JPG, PNG, SVG, TIF, TIFF, WMF, and WMZ.
|
||||
//
|
||||
// The extension should be provided with a "." in front, e.g. ".png".
|
||||
// The width and height should have units in them, e.g. "100pt".
|
||||
func (f *File) AddHeaderFooterImage(sheet string, opts *HeaderFooterImageOptions) error {
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ext, ok := supportedImageTypes[strings.ToLower(opts.Extension)]
|
||||
if !ok {
|
||||
return ErrImgExt
|
||||
}
|
||||
sheetID := f.getSheetID(sheet)
|
||||
vmlID := f.countVMLDrawing() + 1
|
||||
drawingVML := "xl/drawings/vmlDrawing" + strconv.Itoa(vmlID) + ".vml"
|
||||
sheetRelationshipsDrawingVML := "../drawings/vmlDrawing" + strconv.Itoa(vmlID) + ".vml"
|
||||
sheetXMLPath, _ := f.getSheetXMLPath(sheet)
|
||||
sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetXMLPath, "xl/worksheets/") + ".rels"
|
||||
if ws.LegacyDrawingHF != nil {
|
||||
// The worksheet already has a VML relationships, use the relationships drawing ../drawings/vmlDrawing%d.vml.
|
||||
sheetRelationshipsDrawingVML = f.getSheetRelationshipsTargetByID(sheet, ws.LegacyDrawingHF.RID)
|
||||
vmlID, _ = strconv.Atoi(strings.TrimSuffix(strings.TrimPrefix(sheetRelationshipsDrawingVML, "../drawings/vmlDrawing"), ".vml"))
|
||||
drawingVML = strings.ReplaceAll(sheetRelationshipsDrawingVML, "..", "xl")
|
||||
} else {
|
||||
// Add first VML drawing for given sheet.
|
||||
rID := f.addRels(sheetRels, SourceRelationshipDrawingVML, sheetRelationshipsDrawingVML, "")
|
||||
f.addSheetNameSpace(sheet, SourceRelationship)
|
||||
f.addSheetLegacyDrawingHF(sheet, rID)
|
||||
}
|
||||
|
||||
shapeID := map[HeaderFooterImagePositionType]string{
|
||||
HeaderFooterImagePositionLeft: "L",
|
||||
HeaderFooterImagePositionCenter: "C",
|
||||
HeaderFooterImagePositionRight: "R",
|
||||
}[opts.Position] +
|
||||
map[bool]string{false: "H", true: "F"}[opts.IsFooter] +
|
||||
map[bool]string{false: "", true: "FIRST"}[opts.FirstPage]
|
||||
vml := f.VMLDrawing[drawingVML]
|
||||
if vml == nil {
|
||||
vml = &vmlDrawing{
|
||||
XMLNSv: "urn:schemas-microsoft-com:vml",
|
||||
XMLNSo: "urn:schemas-microsoft-com:office:office",
|
||||
XMLNSx: "urn:schemas-microsoft-com:office:excel",
|
||||
ShapeLayout: &xlsxShapeLayout{
|
||||
Ext: "edit", IDmap: &xlsxIDmap{Ext: "edit", Data: sheetID},
|
||||
},
|
||||
ShapeType: &xlsxShapeType{
|
||||
ID: "_x0000_t75",
|
||||
CoordSize: "21600,21600",
|
||||
Spt: 75,
|
||||
PreferRelative: "t",
|
||||
Path: "m@4@5l@4@11@9@11@9@5xe",
|
||||
Filled: "f",
|
||||
Stroked: "f",
|
||||
Stroke: &xlsxStroke{JoinStyle: "miter"},
|
||||
VFormulas: &vFormulas{
|
||||
Formulas: []vFormula{
|
||||
{Equation: "if lineDrawn pixelLineWidth 0"},
|
||||
{Equation: "sum @0 1 0"},
|
||||
{Equation: "sum 0 0 @1"},
|
||||
{Equation: "prod @2 1 2"},
|
||||
{Equation: "prod @3 21600 pixelWidth"},
|
||||
{Equation: "prod @3 21600 pixelHeight"},
|
||||
{Equation: "sum @0 0 1"},
|
||||
{Equation: "prod @6 1 2"},
|
||||
{Equation: "prod @7 21600 pixelWidth"},
|
||||
{Equation: "sum @8 21600 0"},
|
||||
{Equation: "prod @7 21600 pixelHeight"},
|
||||
{Equation: "sum @10 21600 0"},
|
||||
},
|
||||
},
|
||||
VPath: &vPath{ExtrusionOK: "f", GradientShapeOK: "t", ConnectType: "rect"},
|
||||
Lock: &oLock{Ext: "edit", AspectRatio: "t"},
|
||||
},
|
||||
}
|
||||
// Load exist VML shapes from xl/drawings/vmlDrawing%d.vml
|
||||
d, err := f.decodeVMLDrawingReader(drawingVML)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if d != nil {
|
||||
vml.ShapeType.ID = d.ShapeType.ID
|
||||
vml.ShapeType.CoordSize = d.ShapeType.CoordSize
|
||||
vml.ShapeType.Spt = d.ShapeType.Spt
|
||||
vml.ShapeType.PreferRelative = d.ShapeType.PreferRelative
|
||||
vml.ShapeType.Path = d.ShapeType.Path
|
||||
vml.ShapeType.Filled = d.ShapeType.Filled
|
||||
vml.ShapeType.Stroked = d.ShapeType.Stroked
|
||||
for _, v := range d.Shape {
|
||||
s := xlsxShape{
|
||||
ID: v.ID,
|
||||
SpID: v.SpID,
|
||||
Type: v.Type,
|
||||
Style: v.Style,
|
||||
Val: v.Val,
|
||||
}
|
||||
vml.Shape = append(vml.Shape, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for idx, shape := range vml.Shape {
|
||||
if shape.ID == shapeID {
|
||||
vml.Shape = append(vml.Shape[:idx], vml.Shape[idx+1:]...)
|
||||
}
|
||||
}
|
||||
|
||||
style := fmt.Sprintf("position:absolute;margin-left:0;margin-top:0;width:%s;height:%s;z-index:1", opts.Width, opts.Height)
|
||||
drawingVMLRels := "xl/drawings/_rels/vmlDrawing" + strconv.Itoa(vmlID) + ".vml.rels"
|
||||
|
||||
mediaStr := ".." + strings.TrimPrefix(f.addMedia(opts.File, ext), "xl")
|
||||
imageID := f.addRels(drawingVMLRels, SourceRelationshipImage, mediaStr, "")
|
||||
|
||||
shape := xlsxShape{
|
||||
ID: shapeID,
|
||||
SpID: "_x0000_s1025",
|
||||
Type: "#_x0000_t75",
|
||||
Style: style,
|
||||
}
|
||||
sp, _ := xml.Marshal(encodeShape{
|
||||
ImageData: &vImageData{RelID: "rId" + strconv.Itoa(imageID)},
|
||||
Lock: &oLock{Ext: "edit", Rotation: "t"},
|
||||
})
|
||||
|
||||
shape.Val = string(sp[13 : len(sp)-14])
|
||||
vml.Shape = append(vml.Shape, shape)
|
||||
f.VMLDrawing[drawingVML] = vml
|
||||
|
||||
if err := f.setContentTypePartImageExtensions(); err != nil {
|
||||
return err
|
||||
}
|
||||
return f.setContentTypePartVMLExtensions()
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ type vmlDrawing struct {
|
|||
XMLNSv string `xml:"xmlns:v,attr"`
|
||||
XMLNSo string `xml:"xmlns:o,attr"`
|
||||
XMLNSx string `xml:"xmlns:x,attr"`
|
||||
XMLNSmv string `xml:"xmlns:mv,attr"`
|
||||
XMLNSmv string `xml:"xmlns:mv,attr,omitempty"`
|
||||
ShapeLayout *xlsxShapeLayout `xml:"o:shapelayout"`
|
||||
ShapeType *xlsxShapeType `xml:"v:shapetype"`
|
||||
Shape []xlsxShape `xml:"v:shape"`
|
||||
|
@ -44,6 +44,7 @@ type xlsxIDmap struct {
|
|||
type xlsxShape struct {
|
||||
XMLName xml.Name `xml:"v:shape"`
|
||||
ID string `xml:"id,attr"`
|
||||
SpID string `xml:"o:spid,attr,omitempty"`
|
||||
Type string `xml:"type,attr"`
|
||||
Style string `xml:"style,attr"`
|
||||
Button string `xml:"o:button,attr,omitempty"`
|
||||
|
@ -57,12 +58,17 @@ type xlsxShape struct {
|
|||
|
||||
// xlsxShapeType directly maps the shapetype element.
|
||||
type xlsxShapeType struct {
|
||||
ID string `xml:"id,attr"`
|
||||
CoordSize string `xml:"coordsize,attr"`
|
||||
Spt int `xml:"o:spt,attr"`
|
||||
Path string `xml:"path,attr"`
|
||||
Stroke *xlsxStroke `xml:"v:stroke"`
|
||||
VPath *vPath `xml:"v:path"`
|
||||
ID string `xml:"id,attr"`
|
||||
CoordSize string `xml:"coordsize,attr"`
|
||||
Spt int `xml:"o:spt,attr"`
|
||||
PreferRelative string `xml:"o:preferrelative,attr,omitempty"`
|
||||
Path string `xml:"path,attr"`
|
||||
Filled string `xml:"filled,attr,omitempty"`
|
||||
Stroked string `xml:"stroked,attr,omitempty"`
|
||||
Stroke *xlsxStroke `xml:"v:stroke"`
|
||||
VFormulas *vFormulas `xml:"v:formulas"`
|
||||
VPath *vPath `xml:"v:path"`
|
||||
Lock *oLock `xml:"o:lock"`
|
||||
}
|
||||
|
||||
// xlsxStroke directly maps the stroke element.
|
||||
|
@ -72,10 +78,28 @@ type xlsxStroke struct {
|
|||
|
||||
// vPath directly maps the v:path element.
|
||||
type vPath struct {
|
||||
ExtrusionOK string `xml:"o:extrusionok,attr,omitempty"`
|
||||
GradientShapeOK string `xml:"gradientshapeok,attr,omitempty"`
|
||||
ConnectType string `xml:"o:connecttype,attr"`
|
||||
}
|
||||
|
||||
// oLock directly maps the o:lock element.
|
||||
type oLock struct {
|
||||
Ext string `xml:"v:ext,attr"`
|
||||
Rotation string `xml:"rotation,attr,omitempty"`
|
||||
AspectRatio string `xml:"aspectratio,attr,omitempty"`
|
||||
}
|
||||
|
||||
// vFormulas directly maps to the v:formulas element
|
||||
type vFormulas struct {
|
||||
Formulas []vFormula `xml:"v:f"`
|
||||
}
|
||||
|
||||
// vFormula directly maps to the v:f element
|
||||
type vFormula struct {
|
||||
Equation string `xml:"eqn,attr"`
|
||||
}
|
||||
|
||||
// vFill directly maps the v:fill element. This element must be defined within a
|
||||
// Shape element.
|
||||
type vFill struct {
|
||||
|
@ -106,6 +130,13 @@ type vTextBox struct {
|
|||
Div *xlsxDiv `xml:"div"`
|
||||
}
|
||||
|
||||
// vImageData directly maps the v:imagedata element. This element must be
|
||||
// defined within a Shape element.
|
||||
type vImageData struct {
|
||||
RelID string `xml:"o:relid,attr"`
|
||||
Title string `xml:"o:title,attr,omitempty"`
|
||||
}
|
||||
|
||||
// xlsxDiv directly maps the div element.
|
||||
type xlsxDiv struct {
|
||||
Style string `xml:"style,attr"`
|
||||
|
@ -162,15 +193,19 @@ type decodeVmlDrawing struct {
|
|||
// decodeShapeType defines the structure used to parse the shapetype element in
|
||||
// the file xl/drawings/vmlDrawing%d.vml.
|
||||
type decodeShapeType struct {
|
||||
ID string `xml:"id,attr"`
|
||||
CoordSize string `xml:"coordsize,attr"`
|
||||
Spt int `xml:"spt,attr"`
|
||||
Path string `xml:"path,attr"`
|
||||
ID string `xml:"id,attr"`
|
||||
CoordSize string `xml:"coordsize,attr"`
|
||||
Spt int `xml:"spt,attr"`
|
||||
PreferRelative string `xml:"preferrelative,attr,omitempty"`
|
||||
Path string `xml:"path,attr"`
|
||||
Filled string `xml:"filled,attr,omitempty"`
|
||||
Stroked string `xml:"stroked,attr,omitempty"`
|
||||
}
|
||||
|
||||
// decodeShape defines the structure used to parse the particular shape element.
|
||||
type decodeShape struct {
|
||||
ID string `xml:"id,attr"`
|
||||
SpID string `xml:"spid,attr,omitempty"`
|
||||
Type string `xml:"type,attr"`
|
||||
Style string `xml:"style,attr"`
|
||||
Button string `xml:"button,attr,omitempty"`
|
||||
|
@ -254,7 +289,9 @@ type encodeShape struct {
|
|||
Shadow *vShadow `xml:"v:shadow"`
|
||||
Path *vPath `xml:"v:path"`
|
||||
TextBox *vTextBox `xml:"v:textbox"`
|
||||
ImageData *vImageData `xml:"v:imagedata"`
|
||||
ClientData *xClientData `xml:"x:ClientData"`
|
||||
Lock *oLock `xml:"o:lock"`
|
||||
}
|
||||
|
||||
// formCtrlPreset defines the structure used to form control presets.
|
||||
|
@ -301,3 +338,15 @@ type FormControl struct {
|
|||
Type FormControlType
|
||||
Format GraphicOptions
|
||||
}
|
||||
|
||||
// HeaderFooterImageOptions defines the settings for an image to be accessible
|
||||
// from the worksheet header and footer options.
|
||||
type HeaderFooterImageOptions struct {
|
||||
Position HeaderFooterImagePositionType
|
||||
File []byte
|
||||
IsFooter bool
|
||||
FirstPage bool
|
||||
Extension string
|
||||
Width string
|
||||
Height string
|
||||
}
|
||||
|
|
97
vml_test.go
97
vml_test.go
|
@ -412,3 +412,100 @@ func TestExtractFormControl(t *testing.T) {
|
|||
_, err := extractFormControl(string(MacintoshCyrillicCharset))
|
||||
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
|
||||
}
|
||||
|
||||
func TestAddHeaderFooterImage(t *testing.T) {
|
||||
f, sheet, wb := NewFile(), "Sheet1", filepath.Join("test", "TestAddHeaderFooterImage.xlsx")
|
||||
headerFooterOptions := HeaderFooterOptions{
|
||||
DifferentFirst: true,
|
||||
OddHeader: "&L&GExcelize&C&G&R&G",
|
||||
OddFooter: "&L&GExcelize&C&G&R&G",
|
||||
FirstHeader: "&L&GExcelize&C&G&R&G",
|
||||
FirstFooter: "&L&GExcelize&C&G&R&G",
|
||||
}
|
||||
assert.NoError(t, f.SetHeaderFooter(sheet, &headerFooterOptions))
|
||||
assert.NoError(t, f.SetSheetView(sheet, -1, &ViewOptions{View: stringPtr("pageLayout")}))
|
||||
images := map[string][]byte{
|
||||
".wmf": nil, ".tif": nil, ".png": nil,
|
||||
".jpg": nil, ".gif": nil, ".emz": nil, ".emf": nil,
|
||||
}
|
||||
for ext := range images {
|
||||
img, err := os.ReadFile(filepath.Join("test", "images", "excel"+ext))
|
||||
assert.NoError(t, err)
|
||||
images[ext] = img
|
||||
}
|
||||
for _, opt := range []struct {
|
||||
position HeaderFooterImagePositionType
|
||||
file []byte
|
||||
isFooter bool
|
||||
firstPage bool
|
||||
ext string
|
||||
}{
|
||||
{position: HeaderFooterImagePositionLeft, file: images[".tif"], firstPage: true, ext: ".tif"},
|
||||
{position: HeaderFooterImagePositionCenter, file: images[".gif"], firstPage: true, ext: ".gif"},
|
||||
{position: HeaderFooterImagePositionRight, file: images[".png"], firstPage: true, ext: ".png"},
|
||||
{position: HeaderFooterImagePositionLeft, file: images[".emf"], isFooter: true, firstPage: true, ext: ".emf"},
|
||||
{position: HeaderFooterImagePositionCenter, file: images[".wmf"], isFooter: true, firstPage: true, ext: ".wmf"},
|
||||
{position: HeaderFooterImagePositionRight, file: images[".emz"], isFooter: true, firstPage: true, ext: ".emz"},
|
||||
{position: HeaderFooterImagePositionLeft, file: images[".png"], ext: ".png"},
|
||||
{position: HeaderFooterImagePositionCenter, file: images[".png"], ext: ".png"},
|
||||
{position: HeaderFooterImagePositionRight, file: images[".png"], ext: ".png"},
|
||||
{position: HeaderFooterImagePositionLeft, file: images[".tif"], isFooter: true, ext: ".tif"},
|
||||
{position: HeaderFooterImagePositionCenter, file: images[".tif"], isFooter: true, ext: ".tif"},
|
||||
{position: HeaderFooterImagePositionRight, file: images[".tif"], isFooter: true, ext: ".tif"},
|
||||
} {
|
||||
assert.NoError(t, f.AddHeaderFooterImage(sheet, &HeaderFooterImageOptions{
|
||||
Position: opt.position,
|
||||
File: opt.file,
|
||||
IsFooter: opt.isFooter,
|
||||
FirstPage: opt.firstPage,
|
||||
Extension: opt.ext,
|
||||
Width: "50pt",
|
||||
Height: "32pt",
|
||||
}))
|
||||
}
|
||||
assert.NoError(t, f.SetCellValue(sheet, "A1", "Example"))
|
||||
|
||||
// Test add header footer image with not exist sheet
|
||||
assert.EqualError(t, f.AddHeaderFooterImage("SheetN", nil), "sheet SheetN does not exist")
|
||||
// Test add header footer image with unsupported file type
|
||||
assert.Equal(t, f.AddHeaderFooterImage(sheet, &HeaderFooterImageOptions{
|
||||
Extension: "jpg",
|
||||
}), ErrImgExt)
|
||||
assert.NoError(t, f.SaveAs(wb))
|
||||
assert.NoError(t, f.Close())
|
||||
// Test change already exist header image with the different image
|
||||
f, err := OpenFile(wb)
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, f.AddHeaderFooterImage(sheet, &HeaderFooterImageOptions{
|
||||
File: images[".jpg"],
|
||||
FirstPage: true,
|
||||
Extension: ".jpg",
|
||||
Width: "50pt",
|
||||
Height: "32pt",
|
||||
}))
|
||||
assert.NoError(t, f.Save())
|
||||
assert.NoError(t, f.Close())
|
||||
|
||||
// Test add header image with unsupported charset VML drawing
|
||||
f, err = OpenFile(wb)
|
||||
assert.NoError(t, err)
|
||||
f.Pkg.Store("xl/drawings/vmlDrawing1.vml", MacintoshCyrillicCharset)
|
||||
assert.EqualError(t, f.AddHeaderFooterImage(sheet, &HeaderFooterImageOptions{
|
||||
File: images[".jpg"],
|
||||
Extension: ".jpg",
|
||||
Width: "50pt",
|
||||
Height: "32pt",
|
||||
}), "XML syntax error on line 1: invalid UTF-8")
|
||||
assert.NoError(t, f.Close())
|
||||
// Test set legacy drawing header/footer with unsupported charset content types
|
||||
f = NewFile()
|
||||
f.ContentTypes = nil
|
||||
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
|
||||
assert.EqualError(t, f.AddHeaderFooterImage(sheet, &HeaderFooterImageOptions{
|
||||
Extension: ".png",
|
||||
File: images[".png"],
|
||||
Width: "50pt",
|
||||
Height: "32pt",
|
||||
}), "XML syntax error on line 1: invalid UTF-8")
|
||||
assert.NoError(t, f.Close())
|
||||
}
|
||||
|
|
70
xmlChart.go
70
xmlChart.go
|
@ -300,26 +300,26 @@ type cView3D struct {
|
|||
// cPlotArea directly maps the plotArea element. This element specifies the
|
||||
// plot area of the chart.
|
||||
type cPlotArea struct {
|
||||
Layout *string `xml:"layout"`
|
||||
AreaChart *cCharts `xml:"areaChart"`
|
||||
Area3DChart *cCharts `xml:"area3DChart"`
|
||||
BarChart *cCharts `xml:"barChart"`
|
||||
Bar3DChart *cCharts `xml:"bar3DChart"`
|
||||
BubbleChart *cCharts `xml:"bubbleChart"`
|
||||
DoughnutChart *cCharts `xml:"doughnutChart"`
|
||||
LineChart *cCharts `xml:"lineChart"`
|
||||
Line3DChart *cCharts `xml:"line3DChart"`
|
||||
PieChart *cCharts `xml:"pieChart"`
|
||||
Pie3DChart *cCharts `xml:"pie3DChart"`
|
||||
OfPieChart *cCharts `xml:"ofPieChart"`
|
||||
RadarChart *cCharts `xml:"radarChart"`
|
||||
ScatterChart *cCharts `xml:"scatterChart"`
|
||||
Surface3DChart *cCharts `xml:"surface3DChart"`
|
||||
SurfaceChart *cCharts `xml:"surfaceChart"`
|
||||
CatAx []*cAxs `xml:"catAx"`
|
||||
ValAx []*cAxs `xml:"valAx"`
|
||||
SerAx []*cAxs `xml:"serAx"`
|
||||
SpPr *cSpPr `xml:"spPr"`
|
||||
Layout *string `xml:"layout"`
|
||||
AreaChart []*cCharts `xml:"areaChart"`
|
||||
Area3DChart []*cCharts `xml:"area3DChart"`
|
||||
BarChart []*cCharts `xml:"barChart"`
|
||||
Bar3DChart []*cCharts `xml:"bar3DChart"`
|
||||
BubbleChart []*cCharts `xml:"bubbleChart"`
|
||||
DoughnutChart []*cCharts `xml:"doughnutChart"`
|
||||
LineChart []*cCharts `xml:"lineChart"`
|
||||
Line3DChart []*cCharts `xml:"line3DChart"`
|
||||
PieChart []*cCharts `xml:"pieChart"`
|
||||
Pie3DChart []*cCharts `xml:"pie3DChart"`
|
||||
OfPieChart []*cCharts `xml:"ofPieChart"`
|
||||
RadarChart []*cCharts `xml:"radarChart"`
|
||||
ScatterChart []*cCharts `xml:"scatterChart"`
|
||||
Surface3DChart []*cCharts `xml:"surface3DChart"`
|
||||
SurfaceChart []*cCharts `xml:"surfaceChart"`
|
||||
CatAx []*cAxs `xml:"catAx"`
|
||||
ValAx []*cAxs `xml:"valAx"`
|
||||
SerAx []*cAxs `xml:"serAx"`
|
||||
SpPr *cSpPr `xml:"spPr"`
|
||||
}
|
||||
|
||||
// cCharts specifies the common element of the chart.
|
||||
|
@ -530,20 +530,22 @@ type ChartNumFmt struct {
|
|||
|
||||
// ChartAxis directly maps the format settings of the chart axis.
|
||||
type ChartAxis struct {
|
||||
None bool
|
||||
MajorGridLines bool
|
||||
MinorGridLines bool
|
||||
MajorUnit float64
|
||||
TickLabelSkip int
|
||||
ReverseOrder bool
|
||||
Secondary bool
|
||||
Maximum *float64
|
||||
Minimum *float64
|
||||
Font Font
|
||||
LogBase float64
|
||||
NumFmt ChartNumFmt
|
||||
Title []RichTextRun
|
||||
axID int
|
||||
None bool
|
||||
MajorGridLines bool
|
||||
MinorGridLines bool
|
||||
MajorUnit float64
|
||||
TickLabelPosition ChartTickLabelPositionType
|
||||
TickLabelSkip int
|
||||
ReverseOrder bool
|
||||
Secondary bool
|
||||
Maximum *float64
|
||||
Minimum *float64
|
||||
Alignment Alignment
|
||||
Font Font
|
||||
LogBase float64
|
||||
NumFmt ChartNumFmt
|
||||
Title []RichTextRun
|
||||
axID int
|
||||
}
|
||||
|
||||
// ChartDimension directly maps the dimension of the chart.
|
||||
|
|
|
@ -24,7 +24,7 @@ type decodeCellAnchor struct {
|
|||
Sp *decodeSp `xml:"sp"`
|
||||
Pic *decodePic `xml:"pic"`
|
||||
ClientData *decodeClientData `xml:"clientData"`
|
||||
AlternateContent []*xlsxAlternateContent `xml:"mc:AlternateContent"`
|
||||
AlternateContent []*xlsxAlternateContent `xml:"AlternateContent"`
|
||||
Content string `xml:",innerxml"`
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,7 @@ type decodeCellAnchorPos struct {
|
|||
To *xlsxTo `xml:"to"`
|
||||
Pos *xlsxInnerXML `xml:"pos"`
|
||||
Ext *xlsxInnerXML `xml:"ext"`
|
||||
Sp *xlsxInnerXML `xml:"sp"`
|
||||
Sp *xlsxSp `xml:"sp"`
|
||||
GrpSp *xlsxInnerXML `xml:"grpSp"`
|
||||
GraphicFrame *xlsxInnerXML `xml:"graphicFrame"`
|
||||
CxnSp *xlsxInnerXML `xml:"cxnSp"`
|
||||
|
@ -46,19 +46,39 @@ type decodeCellAnchorPos struct {
|
|||
ClientData *xlsxInnerXML `xml:"clientData"`
|
||||
}
|
||||
|
||||
// xdrSp (Shape) directly maps the sp element. This element specifies the
|
||||
// existence of a single shape. A shape can either be a preset or a custom
|
||||
// geometry, defined using the SpreadsheetDrawingML framework. In addition to
|
||||
// a geometry each shape can have both visual and non-visual properties
|
||||
// attached. Text and corresponding styling information can also be attached
|
||||
// to a shape. This shape is specified along with all other shapes within
|
||||
// either the shape tree or group shape elements.
|
||||
type decodeSp struct {
|
||||
NvSpPr *decodeNvSpPr `xml:"nvSpPr"`
|
||||
SpPr *decodeSpPr `xml:"spPr"`
|
||||
// decodeChoice defines the structure used to deserialize the mc:Choice element.
|
||||
type decodeChoice struct {
|
||||
XMLName xml.Name `xml:"Choice"`
|
||||
XMLNSA14 string `xml:"a14,attr"`
|
||||
XMLNSSle15 string `xml:"sle15,attr"`
|
||||
Requires string `xml:"Requires,attr"`
|
||||
GraphicFrame decodeGraphicFrame `xml:"graphicFrame"`
|
||||
}
|
||||
|
||||
// decodeSp (Non-Visual Properties for a Shape) directly maps the nvSpPr
|
||||
// decodeGraphicFrame defines the structure used to deserialize the
|
||||
// xdr:graphicFrame element.
|
||||
type decodeGraphicFrame struct {
|
||||
Macro string `xml:"macro,attr"`
|
||||
NvGraphicFramePr decodeNvGraphicFramePr `xml:"nvGraphicFramePr"`
|
||||
}
|
||||
|
||||
// decodeNvGraphicFramePr defines the structure used to deserialize the
|
||||
// xdr:nvGraphicFramePr element.
|
||||
type decodeNvGraphicFramePr struct {
|
||||
CNvPr decodeCNvPr `xml:"cNvPr"`
|
||||
}
|
||||
|
||||
// decodeSp defines the structure used to deserialize the sp element.
|
||||
type decodeSp struct {
|
||||
Macro string `xml:"macro,attr,omitempty"`
|
||||
TextLink string `xml:"textlink,attr,omitempty"`
|
||||
FLocksText bool `xml:"fLocksText,attr,omitempty"`
|
||||
FPublished *bool `xml:"fPublished,attr"`
|
||||
NvSpPr *decodeNvSpPr `xml:"nvSpPr"`
|
||||
SpPr *decodeSpPr `xml:"spPr"`
|
||||
}
|
||||
|
||||
// decodeNvSpPr (Non-Visual Properties for a Shape) directly maps the nvSpPr
|
||||
// element. This element specifies all non-visual properties for a shape. This
|
||||
// element is a container for the non-visual identification properties, shape
|
||||
// properties and application properties that are to be associated with a
|
||||
|
|
|
@ -238,7 +238,7 @@ type xlsxCellAnchorPos struct {
|
|||
To *xlsxTo `xml:"xdr:to"`
|
||||
Pos *xlsxInnerXML `xml:"xdr:pos"`
|
||||
Ext *xlsxInnerXML `xml:"xdr:ext"`
|
||||
Sp *xlsxInnerXML `xml:"xdr:sp"`
|
||||
Sp *xlsxSp `xml:"xdr:sp"`
|
||||
GrpSp *xlsxInnerXML `xml:"xdr:grpSp"`
|
||||
GraphicFrame *xlsxInnerXML `xml:"xdr:graphicFrame"`
|
||||
CxnSp *xlsxInnerXML `xml:"xdr:cxnSp"`
|
||||
|
@ -248,6 +248,21 @@ type xlsxCellAnchorPos struct {
|
|||
ClientData *xlsxInnerXML `xml:"xdr:clientData"`
|
||||
}
|
||||
|
||||
// xdrSp (Shape) directly maps the sp element. This element specifies the
|
||||
// existence of a single shape. A shape can either be a preset or a custom
|
||||
// geometry, defined using the SpreadsheetDrawingML framework. In addition to
|
||||
// a geometry each shape can have both visual and non-visual properties
|
||||
// attached. Text and corresponding styling information can also be attached
|
||||
// to a shape. This shape is specified along with all other shapes within
|
||||
// either the shape tree or group shape elements.
|
||||
type xlsxSp struct {
|
||||
Macro string `xml:"macro,attr,omitempty"`
|
||||
TextLink string `xml:"textlink,attr,omitempty"`
|
||||
FLocksText bool `xml:"fLocksText,attr,omitempty"`
|
||||
FPublished *bool `xml:"fPublished,attr"`
|
||||
Content string `xml:",innerxml"`
|
||||
}
|
||||
|
||||
// xlsxPoint2D describes the position of a drawing element within a spreadsheet.
|
||||
type xlsxPoint2D struct {
|
||||
XMLName xml.Name `xml:"xdr:pos"`
|
||||
|
@ -409,25 +424,27 @@ type xdrTxBody struct {
|
|||
|
||||
// Picture maps the format settings of the picture.
|
||||
type Picture struct {
|
||||
Extension string
|
||||
File []byte
|
||||
Format *GraphicOptions
|
||||
Extension string
|
||||
File []byte
|
||||
Format *GraphicOptions
|
||||
InsertType PictureInsertType
|
||||
}
|
||||
|
||||
// GraphicOptions directly maps the format settings of the picture.
|
||||
type GraphicOptions struct {
|
||||
AltText string
|
||||
PrintObject *bool
|
||||
Locked *bool
|
||||
LockAspectRatio bool
|
||||
AutoFit bool
|
||||
OffsetX int
|
||||
OffsetY int
|
||||
ScaleX float64
|
||||
ScaleY float64
|
||||
Hyperlink string
|
||||
HyperlinkType string
|
||||
Positioning string
|
||||
AltText string
|
||||
PrintObject *bool
|
||||
Locked *bool
|
||||
LockAspectRatio bool
|
||||
AutoFit bool
|
||||
AutoFitIgnoreAspect bool
|
||||
OffsetX int
|
||||
OffsetY int
|
||||
ScaleX float64
|
||||
ScaleY float64
|
||||
Hyperlink string
|
||||
HyperlinkType string
|
||||
Positioning string
|
||||
}
|
||||
|
||||
// Shape directly maps the format settings of the shape.
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
// Copyright 2016 - 2024 The excelize Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
//
|
||||
// Package excelize providing a set of functions that allow you to write to and
|
||||
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
|
||||
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
|
||||
// Supports complex components by high compatibility, and provided streaming
|
||||
// API for generating or reading data from a worksheet with huge amounts of
|
||||
// data. This library needs Go version 1.18 or later.
|
||||
|
||||
package excelize
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// xlsxMetadata directly maps the metadata element. A cell in a spreadsheet
|
||||
// application can have metadata associated with it. Metadata is just a set of
|
||||
// additional properties about the particular cell, and this metadata is stored
|
||||
// in the metadata xml part. There are two types of metadata: cell metadata and
|
||||
// value metadata. Cell metadata contains information about the cell itself,
|
||||
// and this metadata can be carried along with the cell as it moves
|
||||
// (insert, shift, copy/paste, merge, unmerge, etc). Value metadata is
|
||||
// information about the value of a particular cell. Value metadata properties
|
||||
// can be propagated along with the value as it is referenced in formulas.
|
||||
type xlsxMetadata struct {
|
||||
XMLName xml.Name `xml:"metadata"`
|
||||
MetadataTypes *xlsxInnerXML `xml:"metadataTypes"`
|
||||
MetadataStrings *xlsxInnerXML `xml:"metadataStrings"`
|
||||
MdxMetadata *xlsxInnerXML `xml:"mdxMetadata"`
|
||||
FutureMetadata []xlsxFutureMetadata `xml:"futureMetadata"`
|
||||
CellMetadata *xlsxMetadataBlocks `xml:"cellMetadata"`
|
||||
ValueMetadata *xlsxMetadataBlocks `xml:"valueMetadata"`
|
||||
ExtLst *xlsxInnerXML `xml:"extLst"`
|
||||
}
|
||||
|
||||
// xlsxFutureMetadata directly maps the futureMetadata element. This element
|
||||
// represents future metadata information.
|
||||
type xlsxFutureMetadata struct {
|
||||
Bk []xlsxFutureMetadataBlock `xml:"bk"`
|
||||
ExtLst *xlsxInnerXML `xml:"extLst"`
|
||||
}
|
||||
|
||||
// xlsxFutureMetadataBlock directly maps the kb element. This element represents
|
||||
// a block of future metadata information. This is a location for storing
|
||||
// feature extension information.
|
||||
type xlsxFutureMetadataBlock struct {
|
||||
ExtLst *xlsxInnerXML `xml:"extLst"`
|
||||
}
|
||||
|
||||
// xlsxMetadataBlocks directly maps the metadata element. This element
|
||||
// represents cell metadata information. Cell metadata is information metadata
|
||||
// about a specific cell, and it stays tied to that cell position.
|
||||
type xlsxMetadataBlocks struct {
|
||||
Count int `xml:"count,attr,omitempty"`
|
||||
Bk []xlsxMetadataBlock `xml:"bk"`
|
||||
}
|
||||
|
||||
// xlsxMetadataBlock directly maps the bk element. This element represents a
|
||||
// block of metadata records.
|
||||
type xlsxMetadataBlock struct {
|
||||
Rc []xlsxMetadataRecord `xml:"rc"`
|
||||
}
|
||||
|
||||
// xlsxMetadataRecord directly maps the rc element. This element represents a
|
||||
// reference to a specific metadata record.
|
||||
type xlsxMetadataRecord struct {
|
||||
T int `xml:"t,attr"`
|
||||
V int `xml:"v,attr"`
|
||||
}
|
||||
|
||||
// xlsxRichValueData directly maps the rvData element that specifies rich value
|
||||
// data.
|
||||
type xlsxRichValueData struct {
|
||||
XMLName xml.Name `xml:"rvData"`
|
||||
Count int `xml:"count,attr,omitempty"`
|
||||
Rv []xlsxRichValue `xml:"rv"`
|
||||
ExtLst *xlsxInnerXML `xml:"extLst"`
|
||||
}
|
||||
|
||||
// xlsxRichValue directly maps the rv element that specifies rich value data
|
||||
// information for a single rich value
|
||||
type xlsxRichValue struct {
|
||||
S int `xml:"s,attr"`
|
||||
V []string `xml:"v"`
|
||||
Fb *xlsxInnerXML `xml:"fb"`
|
||||
}
|
||||
|
||||
// xlsxRichValueRels directly maps the richValueRels element. This element that
|
||||
// specifies a list of rich value relationships.
|
||||
type xlsxRichValueRels struct {
|
||||
XMLName xml.Name `xml:"richValueRels"`
|
||||
Rels []xlsxRichValueRelRelationship `xml:"rel"`
|
||||
ExtLst *xlsxInnerXML `xml:"extLst"`
|
||||
}
|
||||
|
||||
// xlsxRichValueRelRelationship directly maps the rel element. This element
|
||||
// specifies a relationship for a rich value property.
|
||||
type xlsxRichValueRelRelationship struct {
|
||||
ID string `xml:"id,attr"`
|
||||
}
|
||||
|
||||
// xlsxWebImagesSupportingRichData directly maps the webImagesSrd element. This
|
||||
// element specifies a list of sets of properties associated with web image rich
|
||||
// values.
|
||||
type xlsxWebImagesSupportingRichData struct {
|
||||
XMLName xml.Name `xml:"webImagesSrd"`
|
||||
WebImageSrd []xlsxWebImageSupportingRichData `xml:"webImageSrd"`
|
||||
ExtLst *xlsxInnerXML `xml:"extLst"`
|
||||
}
|
||||
|
||||
// xlsxWebImageSupportingRichData directly maps the webImageSrd element. This
|
||||
// element specifies a set of properties for a web image rich value.
|
||||
type xlsxWebImageSupportingRichData struct {
|
||||
Address xlsxExternalReference `xml:"address"`
|
||||
MoreImagesAddress xlsxExternalReference `xml:"moreImagesAddress"`
|
||||
Blip xlsxExternalReference `xml:"blip"`
|
||||
}
|
|
@ -56,15 +56,15 @@ type xlsxPivotTableDefinition struct {
|
|||
EnableDrill bool `xml:"enableDrill,attr,omitempty"`
|
||||
EnableFieldProperties bool `xml:"enableFieldProperties,attr,omitempty"`
|
||||
PreserveFormatting bool `xml:"preserveFormatting,attr,omitempty"`
|
||||
UseAutoFormatting *bool `xml:"useAutoFormatting,attr,omitempty"`
|
||||
UseAutoFormatting *bool `xml:"useAutoFormatting,attr"`
|
||||
PageWrap int `xml:"pageWrap,attr,omitempty"`
|
||||
PageOverThenDown *bool `xml:"pageOverThenDown,attr,omitempty"`
|
||||
PageOverThenDown *bool `xml:"pageOverThenDown,attr"`
|
||||
SubtotalHiddenItems bool `xml:"subtotalHiddenItems,attr,omitempty"`
|
||||
RowGrandTotals *bool `xml:"rowGrandTotals,attr,omitempty"`
|
||||
ColGrandTotals *bool `xml:"colGrandTotals,attr,omitempty"`
|
||||
RowGrandTotals *bool `xml:"rowGrandTotals,attr"`
|
||||
ColGrandTotals *bool `xml:"colGrandTotals,attr"`
|
||||
FieldPrintTitles bool `xml:"fieldPrintTitles,attr,omitempty"`
|
||||
ItemPrintTitles bool `xml:"itemPrintTitles,attr,omitempty"`
|
||||
MergeItem *bool `xml:"mergeItem,attr,omitempty"`
|
||||
MergeItem *bool `xml:"mergeItem,attr"`
|
||||
ShowDropZones bool `xml:"showDropZones,attr,omitempty"`
|
||||
CreatedVersion int `xml:"createdVersion,attr,omitempty"`
|
||||
Indent int `xml:"indent,attr,omitempty"`
|
||||
|
@ -74,7 +74,7 @@ type xlsxPivotTableDefinition struct {
|
|||
Compact *bool `xml:"compact,attr"`
|
||||
Outline *bool `xml:"outline,attr"`
|
||||
OutlineData bool `xml:"outlineData,attr,omitempty"`
|
||||
CompactData *bool `xml:"compactData,attr,omitempty"`
|
||||
CompactData *bool `xml:"compactData,attr"`
|
||||
Published bool `xml:"published,attr,omitempty"`
|
||||
GridDropZones bool `xml:"gridDropZones,attr,omitempty"`
|
||||
Immersive bool `xml:"immersive,attr,omitempty"`
|
||||
|
@ -150,7 +150,7 @@ type xlsxPivotField struct {
|
|||
DataSourceSort bool `xml:"dataSourceSort,attr,omitempty"`
|
||||
NonAutoSortDefault bool `xml:"nonAutoSortDefault,attr,omitempty"`
|
||||
RankBy int `xml:"rankBy,attr,omitempty"`
|
||||
DefaultSubtotal *bool `xml:"defaultSubtotal,attr,omitempty"`
|
||||
DefaultSubtotal *bool `xml:"defaultSubtotal,attr"`
|
||||
SumSubtotal bool `xml:"sumSubtotal,attr,omitempty"`
|
||||
CountASubtotal bool `xml:"countASubtotal,attr,omitempty"`
|
||||
AvgSubtotal bool `xml:"avgSubtotal,attr,omitempty"`
|
||||
|
@ -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"`
|
||||
}
|
||||
|
||||
|
|
|
@ -149,9 +149,10 @@ type xlsxX15SlicerCaches struct {
|
|||
// decodeTableSlicerCache defines the structure used to parse the
|
||||
// x15:tableSlicerCache element of the table slicer cache.
|
||||
type decodeTableSlicerCache struct {
|
||||
XMLName xml.Name `xml:"tableSlicerCache"`
|
||||
TableID int `xml:"tableId,attr"`
|
||||
Column int `xml:"column,attr"`
|
||||
XMLName xml.Name `xml:"tableSlicerCache"`
|
||||
TableID int `xml:"tableId,attr"`
|
||||
Column int `xml:"column,attr"`
|
||||
SortOrder string `xml:"sortOrder,attr"`
|
||||
}
|
||||
|
||||
// decodeSlicerList defines the structure used to parse the x14:slicerList
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -308,7 +308,7 @@ type xlsxSheetData struct {
|
|||
// particular row in the worksheet.
|
||||
type xlsxRow struct {
|
||||
C []xlsxC `xml:"c"`
|
||||
R *int `xml:"r,attr"`
|
||||
R int `xml:"r,attr,omitempty"`
|
||||
Spans string `xml:"spans,attr,omitempty"`
|
||||
S int `xml:"s,attr,omitempty"`
|
||||
CustomFormat bool `xml:"customFormat,attr,omitempty"`
|
||||
|
@ -441,6 +441,7 @@ type xlsxDataValidation struct {
|
|||
ShowErrorMessage bool `xml:"showErrorMessage,attr,omitempty"`
|
||||
ShowInputMessage bool `xml:"showInputMessage,attr,omitempty"`
|
||||
Sqref string `xml:"sqref,attr"`
|
||||
XMSqref string `xml:"sqref,omitempty"`
|
||||
Type string `xml:"type,attr,omitempty"`
|
||||
Formula1 *xlsxInnerXML `xml:"formula1"`
|
||||
Formula2 *xlsxInnerXML `xml:"formula2"`
|
||||
|
@ -916,7 +917,7 @@ type ConditionalFormatOptions struct {
|
|||
Type string
|
||||
AboveAverage bool
|
||||
Percent bool
|
||||
Format int
|
||||
Format *int
|
||||
Criteria string
|
||||
Value string
|
||||
MinType string
|
||||
|
@ -1005,6 +1006,9 @@ type PageLayoutOptions struct {
|
|||
FitToWidth *int
|
||||
// BlackAndWhite specified print black and white.
|
||||
BlackAndWhite *bool
|
||||
// PageOrder specifies the ordering of multiple pages. Values
|
||||
// accepted: overThenDown, downThenOver
|
||||
PageOrder *string
|
||||
}
|
||||
|
||||
// ViewOptions directly maps the settings of sheet view.
|
||||
|
|
Loading…
Reference in New Issue