- Correction cell type when formatting date type cell value - Add check for MID and MIDB formula functions num_chars arguments, prevent panic on specifying a negative number - Ref #65, add support for 2 formula functions: SEARCH and SEARCHB - Fix a v2.8.0 regression bug, error on set print area and print titles with built-in special defined name - Add new exported function `GetPivotTables` for get pivot tables - Add a new `Name` field in the `PivotTableOptions` to support specify pivot table name - Using relative cell reference in the pivot table docs and unit tests - Support adding slicer content type part internally - Add new exported source relationship and namespace `NameSpaceSpreadSheetXR10`, `ContentTypeSlicer`, `ContentTypeSlicerCache`, and `SourceRelationshipSlicer` - Add new exported extended URI `ExtURIPivotCacheDefinition` - Fix formula argument wildcard match issues - Update GitHub Actions configuration, test on Go 1.21.x with 1.21.1 and later - Avoid corrupted workbooks generated by improving compatibility with internally indexed color styles
This commit is contained in:
parent
ff5657ba87
commit
ae64bcaabe
|
@ -5,7 +5,7 @@ jobs:
|
|||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.16.x, 1.17.x, 1.18.x, 1.19.x, 1.20.x]
|
||||
go-version: [1.16.x, 1.17.x, 1.18.x, 1.19.x, 1.20.x, '>=1.21.1']
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
targetplatform: [x86, x64]
|
||||
|
||||
|
|
191
calc.go
191
calc.go
|
@ -706,6 +706,8 @@ type formulaFuncs struct {
|
|||
// ROWS
|
||||
// RRI
|
||||
// RSQ
|
||||
// SEARCH
|
||||
// SEARCHB
|
||||
// SEC
|
||||
// SECH
|
||||
// SECOND
|
||||
|
@ -9303,7 +9305,7 @@ func (fn *formulaFuncs) FdotDISTdotRT(argsList *list.List) formulaArg {
|
|||
return fn.FDIST(argsList)
|
||||
}
|
||||
|
||||
// prepareFinvArgs checking and prepare arguments for the formula function
|
||||
// prepareFinvArgs checking and prepare arguments for the formula functions
|
||||
// F.INV, F.INV.RT and FINV.
|
||||
func (fn *formulaFuncs) prepareFinvArgs(name string, argsList *list.List) formulaArg {
|
||||
if argsList.Len() != 3 {
|
||||
|
@ -13612,17 +13614,16 @@ func (fn *formulaFuncs) FINDB(argsList *list.List) formulaArg {
|
|||
return fn.find("FINDB", argsList)
|
||||
}
|
||||
|
||||
// find is an implementation of the formula functions FIND and FINDB.
|
||||
func (fn *formulaFuncs) find(name string, argsList *list.List) formulaArg {
|
||||
// prepareFindArgs checking and prepare arguments for the formula functions
|
||||
// FIND, FINDB, SEARCH and SEARCHB.
|
||||
func (fn *formulaFuncs) prepareFindArgs(name string, argsList *list.List) formulaArg {
|
||||
if argsList.Len() < 2 {
|
||||
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at least 2 arguments", name))
|
||||
}
|
||||
if argsList.Len() > 3 {
|
||||
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s allows at most 3 arguments", name))
|
||||
}
|
||||
findText := argsList.Front().Value.(formulaArg).Value()
|
||||
withinText := argsList.Front().Next().Value.(formulaArg).Value()
|
||||
startNum, result := 1, 1
|
||||
startNum := 1
|
||||
if argsList.Len() == 3 {
|
||||
numArg := argsList.Back().Value.(formulaArg).ToNumber()
|
||||
if numArg.Type != ArgNumber {
|
||||
|
@ -13633,19 +13634,44 @@ func (fn *formulaFuncs) find(name string, argsList *list.List) formulaArg {
|
|||
}
|
||||
startNum = int(numArg.Number)
|
||||
}
|
||||
return newListFormulaArg([]formulaArg{newNumberFormulaArg(float64(startNum))})
|
||||
}
|
||||
|
||||
// find is an implementation of the formula functions FIND, FINDB, SEARCH and
|
||||
// SEARCHB.
|
||||
func (fn *formulaFuncs) find(name string, argsList *list.List) formulaArg {
|
||||
args := fn.prepareFindArgs(name, argsList)
|
||||
if args.Type != ArgList {
|
||||
return args
|
||||
}
|
||||
findText := argsList.Front().Value.(formulaArg).Value()
|
||||
withinText := argsList.Front().Next().Value.(formulaArg).Value()
|
||||
startNum := int(args.List[0].Number)
|
||||
if findText == "" {
|
||||
return newNumberFormulaArg(float64(startNum))
|
||||
}
|
||||
for idx := range withinText {
|
||||
if result < startNum {
|
||||
result++
|
||||
}
|
||||
if strings.Index(withinText[idx:], findText) == 0 {
|
||||
return newNumberFormulaArg(float64(result))
|
||||
}
|
||||
result++
|
||||
dbcs, search := name == "FINDB" || name == "SEARCHB", name == "SEARCH" || name == "SEARCHB"
|
||||
if search {
|
||||
findText, withinText = strings.ToUpper(findText), strings.ToUpper(withinText)
|
||||
}
|
||||
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
|
||||
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))
|
||||
}
|
||||
|
||||
// LEFT function returns a specified number of characters from the start of a
|
||||
|
@ -13780,20 +13806,37 @@ func (fn *formulaFuncs) mid(name string, argsList *list.List) formulaArg {
|
|||
return numCharsArg
|
||||
}
|
||||
startNum := int(startNumArg.Number)
|
||||
if startNum < 0 {
|
||||
if startNum < 1 || numCharsArg.Number < 0 {
|
||||
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
|
||||
}
|
||||
if name == "MIDB" {
|
||||
textLen := len(text)
|
||||
if startNum > textLen {
|
||||
return newStringFormulaArg("")
|
||||
var result string
|
||||
var cnt, offset int
|
||||
for _, char := range text {
|
||||
offset++
|
||||
var dbcs bool
|
||||
if utf8.RuneLen(char) > 1 {
|
||||
dbcs = true
|
||||
offset++
|
||||
}
|
||||
if cnt == int(numCharsArg.Number) {
|
||||
break
|
||||
}
|
||||
if offset+1 > startNum {
|
||||
if dbcs {
|
||||
if cnt+2 > int(numCharsArg.Number) {
|
||||
result += string(char)[:1]
|
||||
break
|
||||
}
|
||||
result += string(char)
|
||||
cnt += 2
|
||||
} else {
|
||||
result += string(char)
|
||||
cnt++
|
||||
}
|
||||
}
|
||||
}
|
||||
startNum--
|
||||
endNum := startNum + int(numCharsArg.Number)
|
||||
if endNum > textLen+1 {
|
||||
return newStringFormulaArg(text[startNum:])
|
||||
}
|
||||
return newStringFormulaArg(text[startNum:endNum])
|
||||
return newStringFormulaArg(result)
|
||||
}
|
||||
// MID
|
||||
textLen := utf8.RuneCountInString(text)
|
||||
|
@ -13922,6 +13965,23 @@ func (fn *formulaFuncs) RIGHTB(argsList *list.List) formulaArg {
|
|||
return fn.leftRight("RIGHTB", argsList)
|
||||
}
|
||||
|
||||
// SEARCH function returns the position of a specified character or sub-string
|
||||
// within a supplied text string. The syntax of the function is:
|
||||
//
|
||||
// SEARCH(search_text,within_text,[start_num])
|
||||
func (fn *formulaFuncs) SEARCH(argsList *list.List) formulaArg {
|
||||
return fn.find("SEARCH", argsList)
|
||||
}
|
||||
|
||||
// SEARCHB functions locate one text string within a second text string, and
|
||||
// return the number of the starting position of the first text string from the
|
||||
// first character of the second text string. The syntax of the function is:
|
||||
//
|
||||
// SEARCHB(search_text,within_text,[start_num])
|
||||
func (fn *formulaFuncs) SEARCHB(argsList *list.List) formulaArg {
|
||||
return fn.find("SEARCHB", argsList)
|
||||
}
|
||||
|
||||
// SUBSTITUTE function replaces one or more instances of a given text string,
|
||||
// within an original text string. The syntax of the function is:
|
||||
//
|
||||
|
@ -14255,46 +14315,57 @@ func (fn *formulaFuncs) CHOOSE(argsList *list.List) formulaArg {
|
|||
return arg.Value.(formulaArg)
|
||||
}
|
||||
|
||||
// deepMatchRune finds whether the text deep matches/satisfies the pattern
|
||||
// string.
|
||||
func deepMatchRune(str, pattern []rune, simple bool) bool {
|
||||
for len(pattern) > 0 {
|
||||
switch pattern[0] {
|
||||
default:
|
||||
if len(str) == 0 || str[0] != pattern[0] {
|
||||
return false
|
||||
}
|
||||
case '?':
|
||||
if len(str) == 0 && !simple {
|
||||
return false
|
||||
}
|
||||
case '*':
|
||||
return deepMatchRune(str, pattern[1:], simple) ||
|
||||
(len(str) > 0 && deepMatchRune(str[1:], pattern, simple))
|
||||
}
|
||||
str = str[1:]
|
||||
pattern = pattern[1:]
|
||||
// matchPatternToRegExp convert find text pattern to regular expression.
|
||||
func matchPatternToRegExp(findText string, dbcs bool) (string, bool) {
|
||||
var (
|
||||
exp string
|
||||
wildCard bool
|
||||
mark = "."
|
||||
)
|
||||
if dbcs {
|
||||
mark = "(?:(?:[\\x00-\\x0081])|(?:[\\xFF61-\\xFFA0])|(?:[\\xF8F1-\\xF8F4])|[0-9A-Za-z])"
|
||||
}
|
||||
return len(str) == 0 && len(pattern) == 0
|
||||
for _, char := range findText {
|
||||
if strings.ContainsAny(string(char), ".+$^[](){}|/") {
|
||||
exp += fmt.Sprintf("\\%s", string(char))
|
||||
continue
|
||||
}
|
||||
if char == '?' {
|
||||
wildCard = true
|
||||
exp += mark
|
||||
continue
|
||||
}
|
||||
if char == '*' {
|
||||
wildCard = true
|
||||
exp += ".*"
|
||||
continue
|
||||
}
|
||||
exp += string(char)
|
||||
}
|
||||
return fmt.Sprintf("^%s", exp), wildCard
|
||||
}
|
||||
|
||||
// matchPattern finds whether the text matches or satisfies the pattern
|
||||
// string. The pattern supports '*' and '?' wildcards in the pattern string.
|
||||
func matchPattern(pattern, name string) (matched bool) {
|
||||
if pattern == "" {
|
||||
return name == pattern
|
||||
func matchPattern(findText, withinText string, dbcs bool, startNum int) (int, bool) {
|
||||
exp, wildCard := matchPatternToRegExp(findText, dbcs)
|
||||
offset := 1
|
||||
for idx := range withinText {
|
||||
if offset < startNum {
|
||||
offset++
|
||||
continue
|
||||
}
|
||||
if wildCard {
|
||||
if ok, _ := regexp.MatchString(exp, withinText[idx:]); ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
if strings.Index(withinText[idx:], findText) == 0 {
|
||||
break
|
||||
}
|
||||
offset++
|
||||
}
|
||||
if pattern == "*" {
|
||||
return true
|
||||
}
|
||||
rName, rPattern := make([]rune, 0, len(name)), make([]rune, 0, len(pattern))
|
||||
for _, r := range name {
|
||||
rName = append(rName, r)
|
||||
}
|
||||
for _, r := range pattern {
|
||||
rPattern = append(rPattern, r)
|
||||
}
|
||||
return deepMatchRune(rName, rPattern, false)
|
||||
return offset, utf8.RuneCountInString(withinText) != offset-1
|
||||
}
|
||||
|
||||
// compareFormulaArg compares the left-hand sides and the right-hand sides'
|
||||
|
@ -14319,7 +14390,7 @@ func compareFormulaArg(lhs, rhs, matchMode formulaArg, caseSensitive bool) byte
|
|||
ls, rs = strings.ToLower(ls), strings.ToLower(rs)
|
||||
}
|
||||
if matchMode.Number == matchModeWildcard {
|
||||
if matchPattern(rs, ls) {
|
||||
if _, ok := matchPattern(rs, ls, false, 0); ok {
|
||||
return criteriaEq
|
||||
}
|
||||
}
|
||||
|
|
60
calc_test.go
60
calc_test.go
|
@ -764,6 +764,30 @@ func TestCalcCellValue(t *testing.T) {
|
|||
"=ROUNDUP(-11.111,2)": "-11.12",
|
||||
"=ROUNDUP(-11.111,-1)": "-20",
|
||||
"=ROUNDUP(ROUNDUP(100,1),-1)": "100",
|
||||
// SEARCH
|
||||
"=SEARCH(\"s\",F1)": "1",
|
||||
"=SEARCH(\"s\",F1,2)": "5",
|
||||
"=SEARCH(\"e\",F1)": "4",
|
||||
"=SEARCH(\"e*\",F1)": "4",
|
||||
"=SEARCH(\"?e\",F1)": "3",
|
||||
"=SEARCH(\"??e\",F1)": "2",
|
||||
"=SEARCH(6,F2)": "2",
|
||||
"=SEARCH(\"?\",\"你好world\")": "1",
|
||||
"=SEARCH(\"?l\",\"你好world\")": "5",
|
||||
"=SEARCH(\"?+\",\"你好 1+2\")": "4",
|
||||
"=SEARCH(\" ?+\",\"你好 1+2\")": "3",
|
||||
// SEARCHB
|
||||
"=SEARCHB(\"s\",F1)": "1",
|
||||
"=SEARCHB(\"s\",F1,2)": "5",
|
||||
"=SEARCHB(\"e\",F1)": "4",
|
||||
"=SEARCHB(\"e*\",F1)": "4",
|
||||
"=SEARCHB(\"?e\",F1)": "3",
|
||||
"=SEARCHB(\"??e\",F1)": "2",
|
||||
"=SEARCHB(6,F2)": "2",
|
||||
"=SEARCHB(\"?\",\"你好world\")": "5",
|
||||
"=SEARCHB(\"?l\",\"你好world\")": "7",
|
||||
"=SEARCHB(\"?+\",\"你好 1+2\")": "6",
|
||||
"=SEARCHB(\" ?+\",\"你好 1+2\")": "5",
|
||||
// SEC
|
||||
"=_xlfn.SEC(-3.14159265358979)": "-1",
|
||||
"=_xlfn.SEC(0)": "1",
|
||||
|
@ -1707,6 +1731,7 @@ func TestCalcCellValue(t *testing.T) {
|
|||
"=FIND(\"i\",\"Original Text\",4)": "5",
|
||||
"=FIND(\"\",\"Original Text\")": "1",
|
||||
"=FIND(\"\",\"Original Text\",2)": "2",
|
||||
"=FIND(\"s\",\"Sales\",2)": "5",
|
||||
// FINDB
|
||||
"=FINDB(\"T\",\"Original Text\")": "10",
|
||||
"=FINDB(\"t\",\"Original Text\")": "13",
|
||||
|
@ -1714,6 +1739,7 @@ func TestCalcCellValue(t *testing.T) {
|
|||
"=FINDB(\"i\",\"Original Text\",4)": "5",
|
||||
"=FINDB(\"\",\"Original Text\")": "1",
|
||||
"=FINDB(\"\",\"Original Text\",2)": "2",
|
||||
"=FINDB(\"s\",\"Sales\",2)": "5",
|
||||
// LEFT
|
||||
"=LEFT(\"Original Text\")": "O",
|
||||
"=LEFT(\"Original Text\",4)": "Orig",
|
||||
|
@ -1752,14 +1778,18 @@ func TestCalcCellValue(t *testing.T) {
|
|||
"=MID(\"255 years\",3,1)": "5",
|
||||
"=MID(\"text\",3,6)": "xt",
|
||||
"=MID(\"text\",6,0)": "",
|
||||
"=MID(\"オリジナルテキスト\",6,4)": "テキスト",
|
||||
"=MID(\"オリジナルテキスト\",3,5)": "ジナルテキ",
|
||||
"=MID(\"你好World\",5,1)": "r",
|
||||
"=MID(\"\u30AA\u30EA\u30B8\u30CA\u30EB\u30C6\u30AD\u30B9\u30C8\",6,4)": "\u30C6\u30AD\u30B9\u30C8",
|
||||
"=MID(\"\u30AA\u30EA\u30B8\u30CA\u30EB\u30C6\u30AD\u30B9\u30C8\",3,5)": "\u30B8\u30CA\u30EB\u30C6\u30AD",
|
||||
// MIDB
|
||||
"=MIDB(\"Original Text\",7,1)": "a",
|
||||
"=MIDB(\"Original Text\",4,7)": "ginal T",
|
||||
"=MIDB(\"255 years\",3,1)": "5",
|
||||
"=MIDB(\"text\",3,6)": "xt",
|
||||
"=MIDB(\"text\",6,0)": "",
|
||||
"=MIDB(\"你好World\",5,1)": "W",
|
||||
"=MIDB(\"\u30AA\u30EA\u30B8\u30CA\u30EB\u30C6\u30AD\u30B9\u30C8\",6,4)": "\u30B8\u30CA",
|
||||
"=MIDB(\"\u30AA\u30EA\u30B8\u30CA\u30EB\u30C6\u30AD\u30B9\u30C8\",3,5)": "\u30EA\u30B8\xe3",
|
||||
// PROPER
|
||||
"=PROPER(\"this is a test sentence\")": "This Is A Test Sentence",
|
||||
"=PROPER(\"THIS IS A TEST SENTENCE\")": "This Is A Test Sentence",
|
||||
|
@ -2695,6 +2725,17 @@ func TestCalcCellValue(t *testing.T) {
|
|||
"=ROUNDUP()": {"#VALUE!", "ROUNDUP requires 2 numeric arguments"},
|
||||
`=ROUNDUP("X",1)`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
|
||||
`=ROUNDUP(1,"X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
|
||||
// SEARCH
|
||||
"=SEARCH()": {"#VALUE!", "SEARCH requires at least 2 arguments"},
|
||||
"=SEARCH(1,A1,1,1)": {"#VALUE!", "SEARCH allows at most 3 arguments"},
|
||||
"=SEARCH(2,A1)": {"#VALUE!", "#VALUE!"},
|
||||
"=SEARCH(1,A1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
|
||||
// SEARCHB
|
||||
"=SEARCHB()": {"#VALUE!", "SEARCHB requires at least 2 arguments"},
|
||||
"=SEARCHB(1,A1,1,1)": {"#VALUE!", "SEARCHB allows at most 3 arguments"},
|
||||
"=SEARCHB(2,A1)": {"#VALUE!", "#VALUE!"},
|
||||
"=SEARCHB(\"?w\",\"你好world\")": {"#VALUE!", "#VALUE!"},
|
||||
"=SEARCHB(1,A1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
|
||||
// SEC
|
||||
"=_xlfn.SEC()": {"#VALUE!", "SEC requires 1 numeric argument"},
|
||||
`=_xlfn.SEC("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
|
||||
|
@ -3781,12 +3822,14 @@ func TestCalcCellValue(t *testing.T) {
|
|||
"=LOWER(1,2)": {"#VALUE!", "LOWER requires 1 argument"},
|
||||
// MID
|
||||
"=MID()": {"#VALUE!", "MID requires 3 arguments"},
|
||||
"=MID(\"\",-1,1)": {"#VALUE!", "#VALUE!"},
|
||||
"=MID(\"\",0,1)": {"#VALUE!", "#VALUE!"},
|
||||
"=MID(\"\",1,-1)": {"#VALUE!", "#VALUE!"},
|
||||
"=MID(\"\",\"\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
|
||||
"=MID(\"\",1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
|
||||
// MIDB
|
||||
"=MIDB()": {"#VALUE!", "MIDB requires 3 arguments"},
|
||||
"=MIDB(\"\",-1,1)": {"#VALUE!", "#VALUE!"},
|
||||
"=MIDB(\"\",0,1)": {"#VALUE!", "#VALUE!"},
|
||||
"=MIDB(\"\",1,-1)": {"#VALUE!", "#VALUE!"},
|
||||
"=MIDB(\"\",\"\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
|
||||
"=MIDB(\"\",1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
|
||||
// PROPER
|
||||
|
@ -4684,14 +4727,6 @@ func TestCalcCompareFormulaArg(t *testing.T) {
|
|||
assert.Equal(t, compareFormulaArg(formulaArg{Type: ArgUnknown}, formulaArg{Type: ArgUnknown}, newNumberFormulaArg(matchModeMaxLess), false), criteriaErr)
|
||||
}
|
||||
|
||||
func TestCalcMatchPattern(t *testing.T) {
|
||||
assert.True(t, matchPattern("", ""))
|
||||
assert.True(t, matchPattern("file/*", "file/abc/bcd/def"))
|
||||
assert.True(t, matchPattern("*", ""))
|
||||
assert.False(t, matchPattern("?", ""))
|
||||
assert.False(t, matchPattern("file/?", "file/abc/bcd/def"))
|
||||
}
|
||||
|
||||
func TestCalcTRANSPOSE(t *testing.T) {
|
||||
cellData := [][]interface{}{
|
||||
{"a", "d"},
|
||||
|
@ -5376,7 +5411,6 @@ func TestCalcXLOOKUP(t *testing.T) {
|
|||
"=XLOOKUP()": {"#VALUE!", "XLOOKUP requires at least 3 arguments"},
|
||||
"=XLOOKUP($C3,$C5:$C5,$C6:$C17,NA(),0,2,1)": {"#VALUE!", "XLOOKUP allows at most 6 arguments"},
|
||||
"=XLOOKUP($C3,$C5,$C6,NA(),0,2)": {"#N/A", "#N/A"},
|
||||
"=XLOOKUP(\"?\",B2:B9,C2:C9,NA(),2)": {"#N/A", "#N/A"},
|
||||
"=XLOOKUP($C3,$C4:$D5,$C6:$C17,NA(),0,2)": {"#VALUE!", "#VALUE!"},
|
||||
"=XLOOKUP($C3,$C5:$C5,$C6:$G17,NA(),0,-2)": {"#VALUE!", "#VALUE!"},
|
||||
"=XLOOKUP($C3,$C5:$G5,$C6:$F7,NA(),0,2)": {"#VALUE!", "#VALUE!"},
|
||||
|
|
2
cell.go
2
cell.go
|
@ -565,7 +565,7 @@ func (c *xlsxC) getCellDate(f *File, raw bool) (string, error) {
|
|||
c.V = strconv.FormatFloat(excelTime, 'G', 15, 64)
|
||||
}
|
||||
}
|
||||
return f.formattedValue(c, raw, CellTypeBool)
|
||||
return f.formattedValue(c, raw, CellTypeDate)
|
||||
}
|
||||
|
||||
// getValueFrom return a value from a column/row cell, this function is
|
||||
|
|
|
@ -506,6 +506,8 @@ func (f *File) addContentTypePart(index int, contentType string) error {
|
|||
"pivotTable": "/xl/pivotTables/pivotTable" + strconv.Itoa(index) + ".xml",
|
||||
"pivotCache": "/xl/pivotCache/pivotCacheDefinition" + strconv.Itoa(index) + ".xml",
|
||||
"sharedStrings": "/xl/sharedStrings.xml",
|
||||
"slicer": "/xl/slicers/slicer" + strconv.Itoa(index) + ".xml",
|
||||
"slicerCache": "/xl/slicerCaches/slicerCache" + strconv.Itoa(index) + ".xml",
|
||||
}
|
||||
contentTypes := map[string]string{
|
||||
"chart": ContentTypeDrawingML,
|
||||
|
@ -516,6 +518,8 @@ func (f *File) addContentTypePart(index int, contentType string) error {
|
|||
"pivotTable": ContentTypeSpreadSheetMLPivotTable,
|
||||
"pivotCache": ContentTypeSpreadSheetMLPivotCacheDefinition,
|
||||
"sharedStrings": ContentTypeSpreadSheetMLSharedStrings,
|
||||
"slicer": ContentTypeSlicer,
|
||||
"slicerCache": ContentTypeSlicerCache,
|
||||
}
|
||||
s, ok := setContentType[contentType]
|
||||
if ok {
|
||||
|
|
183
pivotTable.go
183
pivotTable.go
|
@ -12,10 +12,17 @@
|
|||
package excelize
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
// PivotTableOptions directly maps the format settings of the pivot table.
|
||||
|
@ -29,6 +36,7 @@ type PivotTableOptions struct {
|
|||
pivotTableSheetName string
|
||||
DataRange string
|
||||
PivotTableRange string
|
||||
Name string
|
||||
Rows []PivotTableField
|
||||
Columns []PivotTableField
|
||||
Data []PivotTableField
|
||||
|
@ -115,8 +123,8 @@ type PivotTableField struct {
|
|||
// f.SetCellValue("Sheet1", fmt.Sprintf("E%d", row), region[rand.Intn(4)])
|
||||
// }
|
||||
// if err := f.AddPivotTable(&excelize.PivotTableOptions{
|
||||
// DataRange: "Sheet1!$A$1:$E$31",
|
||||
// PivotTableRange: "Sheet1!$G$2:$M$34",
|
||||
// DataRange: "Sheet1!A1:E31",
|
||||
// PivotTableRange: "Sheet1!G2:M34",
|
||||
// Rows: []excelize.PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
|
||||
// Filter: []excelize.PivotTableField{{Data: "Region"}},
|
||||
// Columns: []excelize.PivotTableField{{Data: "Type", DefaultSubtotal: true}},
|
||||
|
@ -181,6 +189,9 @@ func (f *File) parseFormatPivotTableSet(opts *PivotTableOptions) (*xlsxWorksheet
|
|||
if err != nil {
|
||||
return nil, "", fmt.Errorf("parameter 'PivotTableRange' parsing error: %s", err.Error())
|
||||
}
|
||||
if len(opts.Name) > MaxFieldLength {
|
||||
return nil, "", ErrNameLength
|
||||
}
|
||||
opts.pivotTableSheetName = pivotTableSheetName
|
||||
dataRange := f.getDefinedNameRefTo(opts.DataRange, pivotTableSheetName)
|
||||
if dataRange == "" {
|
||||
|
@ -334,7 +345,7 @@ func (f *File) addPivotTable(cacheID, pivotTableID int, pivotTableXML string, op
|
|||
return opts.PivotTableStyleName
|
||||
}
|
||||
pt := xlsxPivotTableDefinition{
|
||||
Name: fmt.Sprintf("Pivot Table%d", pivotTableID),
|
||||
Name: opts.Name,
|
||||
CacheID: cacheID,
|
||||
RowGrandTotals: &opts.RowGrandTotals,
|
||||
ColGrandTotals: &opts.ColGrandTotals,
|
||||
|
@ -376,7 +387,9 @@ func (f *File) addPivotTable(cacheID, pivotTableID int, pivotTableXML string, op
|
|||
ShowLastColumn: opts.ShowLastColumn,
|
||||
},
|
||||
}
|
||||
|
||||
if pt.Name == "" {
|
||||
pt.Name = fmt.Sprintf("PivotTable%d", pivotTableID)
|
||||
}
|
||||
// pivot fields
|
||||
_ = f.addPivotFields(&pt, opts)
|
||||
|
||||
|
@ -604,8 +617,8 @@ func (f *File) addPivotFields(pt *xlsxPivotTableDefinition, opts *PivotTableOpti
|
|||
return err
|
||||
}
|
||||
|
||||
// countPivotTables provides a function to get drawing files count storage in
|
||||
// the folder xl/pivotTables.
|
||||
// countPivotTables provides a function to get pivot table files count storage
|
||||
// in the folder xl/pivotTables.
|
||||
func (f *File) countPivotTables() int {
|
||||
count := 0
|
||||
f.Pkg.Range(func(k, v interface{}) bool {
|
||||
|
@ -617,8 +630,8 @@ func (f *File) countPivotTables() int {
|
|||
return count
|
||||
}
|
||||
|
||||
// countPivotCache provides a function to get drawing files count storage in
|
||||
// the folder xl/pivotCache.
|
||||
// countPivotCache provides a function to get pivot table cache definition files
|
||||
// count storage in the folder xl/pivotCache.
|
||||
func (f *File) countPivotCache() int {
|
||||
count := 0
|
||||
f.Pkg.Range(func(k, v interface{}) bool {
|
||||
|
@ -719,3 +732,157 @@ func (f *File) addWorkbookPivotCache(RID int) int {
|
|||
})
|
||||
return cacheID
|
||||
}
|
||||
|
||||
// GetPivotTables returns all pivot table definitions in a worksheet by given
|
||||
// worksheet name.
|
||||
func (f *File) GetPivotTables(sheet string) ([]PivotTableOptions, error) {
|
||||
var pivotTables []PivotTableOptions
|
||||
name, ok := f.getSheetXMLPath(sheet)
|
||||
if !ok {
|
||||
return pivotTables, newNoExistSheetError(sheet)
|
||||
}
|
||||
rels := "xl/worksheets/_rels/" + strings.TrimPrefix(name, "xl/worksheets/") + ".rels"
|
||||
sheetRels, err := f.relsReader(rels)
|
||||
if err != nil {
|
||||
return pivotTables, err
|
||||
}
|
||||
if sheetRels == nil {
|
||||
sheetRels = &xlsxRelationships{}
|
||||
}
|
||||
for _, v := range sheetRels.Relationships {
|
||||
if v.Type == SourceRelationshipPivotTable {
|
||||
pivotTableXML := strings.ReplaceAll(v.Target, "..", "xl")
|
||||
pivotCacheRels := "xl/pivotTables/_rels/" + filepath.Base(v.Target) + ".rels"
|
||||
pivotTable, err := f.getPivotTable(sheet, pivotTableXML, pivotCacheRels)
|
||||
if err != nil {
|
||||
return pivotTables, err
|
||||
}
|
||||
pivotTables = append(pivotTables, pivotTable)
|
||||
}
|
||||
}
|
||||
return pivotTables, nil
|
||||
}
|
||||
|
||||
// getPivotTable provides a function to get a pivot table definition by given
|
||||
// worksheet name, pivot table XML path and pivot cache relationship XML path.
|
||||
func (f *File) getPivotTable(sheet, pivotTableXML, pivotCacheRels string) (PivotTableOptions, error) {
|
||||
var opts PivotTableOptions
|
||||
rels, err := f.relsReader(pivotCacheRels)
|
||||
if err != nil {
|
||||
return opts, err
|
||||
}
|
||||
var pivotCacheXML string
|
||||
for _, v := range rels.Relationships {
|
||||
if v.Type == SourceRelationshipPivotCache {
|
||||
pivotCacheXML = strings.ReplaceAll(v.Target, "..", "xl")
|
||||
break
|
||||
}
|
||||
}
|
||||
pc, err := f.pivotCacheReader(pivotCacheXML)
|
||||
if err != nil {
|
||||
return opts, err
|
||||
}
|
||||
pt, err := f.pivotTableReader(pivotTableXML)
|
||||
if err != nil {
|
||||
return opts, err
|
||||
}
|
||||
dataRange := fmt.Sprintf("%s!%s", pc.CacheSource.WorksheetSource.Sheet, pc.CacheSource.WorksheetSource.Ref)
|
||||
opts = PivotTableOptions{
|
||||
pivotTableSheetName: sheet,
|
||||
DataRange: dataRange,
|
||||
PivotTableRange: fmt.Sprintf("%s!%s", sheet, pt.Location.Ref),
|
||||
Name: pt.Name,
|
||||
}
|
||||
fields := []string{"RowGrandTotals", "ColGrandTotals", "ShowDrill", "UseAutoFormatting", "PageOverThenDown", "MergeItem", "CompactData", "ShowError"}
|
||||
immutable, mutable := reflect.ValueOf(*pt), reflect.ValueOf(&opts).Elem()
|
||||
for _, field := range fields {
|
||||
immutableField := immutable.FieldByName(field)
|
||||
if immutableField.Kind() == reflect.Ptr && !immutableField.IsNil() && immutableField.Elem().Kind() == reflect.Bool {
|
||||
mutable.FieldByName(field).SetBool(immutableField.Elem().Bool())
|
||||
}
|
||||
}
|
||||
if si := pt.PivotTableStyleInfo; si != nil {
|
||||
opts.ShowRowHeaders = si.ShowRowHeaders
|
||||
opts.ShowColHeaders = si.ShowColHeaders
|
||||
opts.ShowRowStripes = si.ShowRowStripes
|
||||
opts.ShowColStripes = si.ShowColStripes
|
||||
opts.ShowLastColumn = si.ShowLastColumn
|
||||
opts.PivotTableStyleName = si.Name
|
||||
}
|
||||
order, _ := f.getPivotFieldsOrder(&PivotTableOptions{DataRange: dataRange, pivotTableSheetName: pt.Name})
|
||||
f.extractPivotTableFields(order, pt, &opts)
|
||||
return opts, err
|
||||
}
|
||||
|
||||
// pivotTableReader provides a function to get the pointer to the structure
|
||||
// after deserialization of xl/pivotTables/pivotTable%d.xml.
|
||||
func (f *File) pivotTableReader(path string) (*xlsxPivotTableDefinition, error) {
|
||||
content, ok := f.Pkg.Load(path)
|
||||
pivotTable := &xlsxPivotTableDefinition{}
|
||||
if ok && content != nil {
|
||||
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(content.([]byte)))).
|
||||
Decode(pivotTable); err != nil && err != io.EOF {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return pivotTable, nil
|
||||
}
|
||||
|
||||
// pivotCacheReader provides a function to get the pointer to the structure
|
||||
// after deserialization of xl/pivotCache/pivotCacheDefinition%d.xml.
|
||||
func (f *File) pivotCacheReader(path string) (*xlsxPivotCacheDefinition, error) {
|
||||
content, ok := f.Pkg.Load(path)
|
||||
pivotCache := &xlsxPivotCacheDefinition{}
|
||||
if ok && content != nil {
|
||||
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(content.([]byte)))).
|
||||
Decode(pivotCache); err != nil && err != io.EOF {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return pivotCache, nil
|
||||
}
|
||||
|
||||
// extractPivotTableFields provides a function to extract all pivot table fields
|
||||
// settings by given pivot table fields.
|
||||
func (f *File) extractPivotTableFields(order []string, pt *xlsxPivotTableDefinition, opts *PivotTableOptions) {
|
||||
for fieldIdx, field := range pt.PivotFields.PivotField {
|
||||
if field.Axis == "axisRow" {
|
||||
opts.Rows = append(opts.Rows, extractPivotTableField(order[fieldIdx], field))
|
||||
}
|
||||
if field.Axis == "axisCol" {
|
||||
opts.Columns = append(opts.Columns, extractPivotTableField(order[fieldIdx], field))
|
||||
}
|
||||
if field.Axis == "axisPage" {
|
||||
opts.Filter = append(opts.Filter, extractPivotTableField(order[fieldIdx], field))
|
||||
}
|
||||
}
|
||||
if pt.DataFields != nil {
|
||||
for _, field := range pt.DataFields.DataField {
|
||||
opts.Data = append(opts.Data, PivotTableField{
|
||||
Data: order[field.Fld],
|
||||
Name: field.Name,
|
||||
Subtotal: cases.Title(language.English).String(field.Subtotal),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// extractPivotTableField provides a function to extract pivot table field
|
||||
// settings by given pivot table fields.
|
||||
func extractPivotTableField(data string, fld *xlsxPivotField) PivotTableField {
|
||||
pivotTableField := PivotTableField{
|
||||
Data: data,
|
||||
}
|
||||
fields := []string{"Compact", "Name", "Outline", "Subtotal", "DefaultSubtotal"}
|
||||
immutable, mutable := reflect.ValueOf(*fld), reflect.ValueOf(&pivotTableField).Elem()
|
||||
for _, field := range fields {
|
||||
immutableField := immutable.FieldByName(field)
|
||||
if immutableField.Kind() == reflect.String {
|
||||
mutable.FieldByName(field).SetString(immutableField.String())
|
||||
}
|
||||
if immutableField.Kind() == reflect.Ptr && !immutableField.IsNil() && immutableField.Elem().Kind() == reflect.Bool {
|
||||
mutable.FieldByName(field).SetBool(immutableField.Elem().Bool())
|
||||
}
|
||||
}
|
||||
return pivotTableField
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAddPivotTable(t *testing.T) {
|
||||
func TestPivotTable(t *testing.T) {
|
||||
f := NewFile()
|
||||
// Create some data in a sheet
|
||||
month := []string{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}
|
||||
|
@ -25,25 +25,33 @@ func TestAddPivotTable(t *testing.T) {
|
|||
assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("D%d", row), rand.Intn(5000)))
|
||||
assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("E%d", row), region[rand.Intn(4)]))
|
||||
}
|
||||
assert.NoError(t, f.AddPivotTable(&PivotTableOptions{
|
||||
DataRange: "Sheet1!$A$1:$E$31",
|
||||
PivotTableRange: "Sheet1!$G$2:$M$34",
|
||||
Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
|
||||
Filter: []PivotTableField{{Data: "Region"}},
|
||||
Columns: []PivotTableField{{Data: "Type", DefaultSubtotal: true}},
|
||||
Data: []PivotTableField{{Data: "Sales", Subtotal: "Sum", Name: "Summarize by Sum"}},
|
||||
RowGrandTotals: true,
|
||||
ColGrandTotals: true,
|
||||
ShowDrill: true,
|
||||
ShowRowHeaders: true,
|
||||
ShowColHeaders: true,
|
||||
ShowLastColumn: true,
|
||||
ShowError: true,
|
||||
}))
|
||||
expected := &PivotTableOptions{
|
||||
DataRange: "Sheet1!A1:E31",
|
||||
PivotTableRange: "Sheet1!G2:M34",
|
||||
Name: "PivotTable1",
|
||||
Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
|
||||
Filter: []PivotTableField{{Data: "Region"}},
|
||||
Columns: []PivotTableField{{Data: "Type", DefaultSubtotal: true}},
|
||||
Data: []PivotTableField{{Data: "Sales", Subtotal: "Sum", Name: "Summarize by Sum"}},
|
||||
RowGrandTotals: true,
|
||||
ColGrandTotals: true,
|
||||
ShowDrill: true,
|
||||
ShowRowHeaders: true,
|
||||
ShowColHeaders: true,
|
||||
ShowLastColumn: true,
|
||||
ShowError: true,
|
||||
PivotTableStyleName: "PivotStyleLight16",
|
||||
}
|
||||
assert.NoError(t, f.AddPivotTable(expected))
|
||||
// Test get pivot table
|
||||
pivotTables, err := f.GetPivotTables("Sheet1")
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, pivotTables, 1)
|
||||
assert.Equal(t, *expected, pivotTables[0])
|
||||
// Use different order of coordinate tests
|
||||
assert.NoError(t, f.AddPivotTable(&PivotTableOptions{
|
||||
DataRange: "Sheet1!$A$1:$E$31",
|
||||
PivotTableRange: "Sheet1!$U$34:$O$2",
|
||||
DataRange: "Sheet1!A1:E31",
|
||||
PivotTableRange: "Sheet1!U34:O2",
|
||||
Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
|
||||
Columns: []PivotTableField{{Data: "Type", DefaultSubtotal: true}},
|
||||
Data: []PivotTableField{{Data: "Sales", Subtotal: "Average", Name: "Summarize by Average"}},
|
||||
|
@ -54,10 +62,15 @@ func TestAddPivotTable(t *testing.T) {
|
|||
ShowColHeaders: true,
|
||||
ShowLastColumn: true,
|
||||
}))
|
||||
// Test get pivot table with default style name
|
||||
pivotTables, err = f.GetPivotTables("Sheet1")
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, pivotTables, 2)
|
||||
assert.Equal(t, "PivotStyleLight16", pivotTables[1].PivotTableStyleName)
|
||||
|
||||
assert.NoError(t, f.AddPivotTable(&PivotTableOptions{
|
||||
DataRange: "Sheet1!$A$1:$E$31",
|
||||
PivotTableRange: "Sheet1!$W$2:$AC$34",
|
||||
DataRange: "Sheet1!A1:E31",
|
||||
PivotTableRange: "Sheet1!W2:AC34",
|
||||
Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
|
||||
Columns: []PivotTableField{{Data: "Region"}},
|
||||
Data: []PivotTableField{{Data: "Sales", Subtotal: "Count", Name: "Summarize by Count"}},
|
||||
|
@ -69,8 +82,8 @@ func TestAddPivotTable(t *testing.T) {
|
|||
ShowLastColumn: true,
|
||||
}))
|
||||
assert.NoError(t, f.AddPivotTable(&PivotTableOptions{
|
||||
DataRange: "Sheet1!$A$1:$E$31",
|
||||
PivotTableRange: "Sheet1!$G$42:$W$55",
|
||||
DataRange: "Sheet1!A1:E31",
|
||||
PivotTableRange: "Sheet1!G42:W55",
|
||||
Rows: []PivotTableField{{Data: "Month"}},
|
||||
Columns: []PivotTableField{{Data: "Region", DefaultSubtotal: true}, {Data: "Year"}},
|
||||
Data: []PivotTableField{{Data: "Sales", Subtotal: "CountNums", Name: "Summarize by CountNums"}},
|
||||
|
@ -82,8 +95,8 @@ func TestAddPivotTable(t *testing.T) {
|
|||
ShowLastColumn: true,
|
||||
}))
|
||||
assert.NoError(t, f.AddPivotTable(&PivotTableOptions{
|
||||
DataRange: "Sheet1!$A$1:$E$31",
|
||||
PivotTableRange: "Sheet1!$AE$2:$AG$33",
|
||||
DataRange: "Sheet1!A1:E31",
|
||||
PivotTableRange: "Sheet1!AE2:AG33",
|
||||
Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
|
||||
Data: []PivotTableField{{Data: "Sales", Subtotal: "Max", Name: "Summarize by Max"}, {Data: "Sales", Subtotal: "Average", Name: "Average of Sales"}},
|
||||
RowGrandTotals: true,
|
||||
|
@ -95,8 +108,8 @@ func TestAddPivotTable(t *testing.T) {
|
|||
}))
|
||||
// Create pivot table with empty subtotal field name and specified style
|
||||
assert.NoError(t, f.AddPivotTable(&PivotTableOptions{
|
||||
DataRange: "Sheet1!$A$1:$E$31",
|
||||
PivotTableRange: "Sheet1!$AJ$2:$AP1$35",
|
||||
DataRange: "Sheet1!A1:E31",
|
||||
PivotTableRange: "Sheet1!AJ2:AP135",
|
||||
Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
|
||||
Filter: []PivotTableField{{Data: "Region"}},
|
||||
Columns: []PivotTableField{},
|
||||
|
@ -109,11 +122,11 @@ func TestAddPivotTable(t *testing.T) {
|
|||
ShowLastColumn: true,
|
||||
PivotTableStyleName: "PivotStyleLight19",
|
||||
}))
|
||||
_, err := f.NewSheet("Sheet2")
|
||||
_, err = f.NewSheet("Sheet2")
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, f.AddPivotTable(&PivotTableOptions{
|
||||
DataRange: "Sheet1!$A$1:$E$31",
|
||||
PivotTableRange: "Sheet2!$A$1:$AN$17",
|
||||
DataRange: "Sheet1!A1:E31",
|
||||
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"}},
|
||||
|
@ -125,8 +138,8 @@ func TestAddPivotTable(t *testing.T) {
|
|||
ShowLastColumn: true,
|
||||
}))
|
||||
assert.NoError(t, f.AddPivotTable(&PivotTableOptions{
|
||||
DataRange: "Sheet1!$A$1:$E$31",
|
||||
PivotTableRange: "Sheet2!$A$20:$AR$60",
|
||||
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"}},
|
||||
|
@ -140,13 +153,13 @@ func TestAddPivotTable(t *testing.T) {
|
|||
// Create pivot table with many data, many rows, many cols and defined name
|
||||
assert.NoError(t, f.SetDefinedName(&DefinedName{
|
||||
Name: "dataRange",
|
||||
RefersTo: "Sheet1!$A$1:$E$31",
|
||||
RefersTo: "Sheet1!A1:E31",
|
||||
Comment: "Pivot Table Data Range",
|
||||
Scope: "Sheet2",
|
||||
}))
|
||||
assert.NoError(t, f.AddPivotTable(&PivotTableOptions{
|
||||
DataRange: "dataRange",
|
||||
PivotTableRange: "Sheet2!$A$65:$AJ$100",
|
||||
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"}},
|
||||
|
@ -160,58 +173,64 @@ func TestAddPivotTable(t *testing.T) {
|
|||
|
||||
// Test empty pivot table options
|
||||
assert.EqualError(t, f.AddPivotTable(nil), ErrParameterRequired.Error())
|
||||
// Test add pivot table with custom name which exceeds the max characters limit
|
||||
assert.Equal(t, ErrNameLength, f.AddPivotTable(&PivotTableOptions{
|
||||
DataRange: "dataRange",
|
||||
PivotTableRange: "Sheet2!A65:AJ100",
|
||||
Name: strings.Repeat("c", MaxFieldLength+1),
|
||||
}))
|
||||
// Test invalid data range
|
||||
assert.EqualError(t, f.AddPivotTable(&PivotTableOptions{
|
||||
DataRange: "Sheet1!$A$1:$A$1",
|
||||
PivotTableRange: "Sheet1!$U$34:$O$2",
|
||||
DataRange: "Sheet1!A1:A1",
|
||||
PivotTableRange: "Sheet1!U34:O2",
|
||||
Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
|
||||
Columns: []PivotTableField{{Data: "Type", DefaultSubtotal: true}},
|
||||
Data: []PivotTableField{{Data: "Sales"}},
|
||||
}), `parameter 'DataRange' parsing error: parameter is invalid`)
|
||||
// Test the data range of the worksheet that is not declared
|
||||
assert.EqualError(t, f.AddPivotTable(&PivotTableOptions{
|
||||
DataRange: "$A$1:$E$31",
|
||||
PivotTableRange: "Sheet1!$U$34:$O$2",
|
||||
DataRange: "A1:E31",
|
||||
PivotTableRange: "Sheet1!U34:O2",
|
||||
Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
|
||||
Columns: []PivotTableField{{Data: "Type", DefaultSubtotal: true}},
|
||||
Data: []PivotTableField{{Data: "Sales"}},
|
||||
}), `parameter 'DataRange' parsing error: parameter is invalid`)
|
||||
// Test the worksheet declared in the data range does not exist
|
||||
assert.EqualError(t, f.AddPivotTable(&PivotTableOptions{
|
||||
DataRange: "SheetN!$A$1:$E$31",
|
||||
PivotTableRange: "Sheet1!$U$34:$O$2",
|
||||
DataRange: "SheetN!A1:E31",
|
||||
PivotTableRange: "Sheet1!U34:O2",
|
||||
Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
|
||||
Columns: []PivotTableField{{Data: "Type", DefaultSubtotal: true}},
|
||||
Data: []PivotTableField{{Data: "Sales"}},
|
||||
}), "sheet SheetN does not exist")
|
||||
// Test the pivot table range of the worksheet that is not declared
|
||||
assert.EqualError(t, f.AddPivotTable(&PivotTableOptions{
|
||||
DataRange: "Sheet1!$A$1:$E$31",
|
||||
PivotTableRange: "$U$34:$O$2",
|
||||
DataRange: "Sheet1!A1:E31",
|
||||
PivotTableRange: "U34:O2",
|
||||
Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
|
||||
Columns: []PivotTableField{{Data: "Type", DefaultSubtotal: true}},
|
||||
Data: []PivotTableField{{Data: "Sales"}},
|
||||
}), `parameter 'PivotTableRange' parsing error: parameter is invalid`)
|
||||
// Test the worksheet declared in the pivot table range does not exist
|
||||
assert.EqualError(t, f.AddPivotTable(&PivotTableOptions{
|
||||
DataRange: "Sheet1!$A$1:$E$31",
|
||||
PivotTableRange: "SheetN!$U$34:$O$2",
|
||||
DataRange: "Sheet1!A1:E31",
|
||||
PivotTableRange: "SheetN!U34:O2",
|
||||
Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
|
||||
Columns: []PivotTableField{{Data: "Type", DefaultSubtotal: true}},
|
||||
Data: []PivotTableField{{Data: "Sales"}},
|
||||
}), "sheet SheetN does not exist")
|
||||
// Test not exists worksheet in data range
|
||||
assert.EqualError(t, f.AddPivotTable(&PivotTableOptions{
|
||||
DataRange: "SheetN!$A$1:$E$31",
|
||||
PivotTableRange: "Sheet1!$U$34:$O$2",
|
||||
DataRange: "SheetN!A1:E31",
|
||||
PivotTableRange: "Sheet1!U34:O2",
|
||||
Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
|
||||
Columns: []PivotTableField{{Data: "Type", DefaultSubtotal: true}},
|
||||
Data: []PivotTableField{{Data: "Sales"}},
|
||||
}), "sheet SheetN does not exist")
|
||||
// Test invalid row number in data range
|
||||
assert.EqualError(t, f.AddPivotTable(&PivotTableOptions{
|
||||
DataRange: "Sheet1!$A$0:$E$31",
|
||||
PivotTableRange: "Sheet1!$U$34:$O$2",
|
||||
DataRange: "Sheet1!A0:E31",
|
||||
PivotTableRange: "Sheet1!U34:O2",
|
||||
Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
|
||||
Columns: []PivotTableField{{Data: "Type", DefaultSubtotal: true}},
|
||||
Data: []PivotTableField{{Data: "Sales"}},
|
||||
|
@ -219,8 +238,8 @@ func TestAddPivotTable(t *testing.T) {
|
|||
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddPivotTable1.xlsx")))
|
||||
// Test with field names that exceed the length limit and invalid subtotal
|
||||
assert.NoError(t, f.AddPivotTable(&PivotTableOptions{
|
||||
DataRange: "Sheet1!$A$1:$E$31",
|
||||
PivotTableRange: "Sheet1!$G$2:$M$34",
|
||||
DataRange: "Sheet1!A1:E31",
|
||||
PivotTableRange: "Sheet1!G2:M34",
|
||||
Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
|
||||
Columns: []PivotTableField{{Data: "Type", DefaultSubtotal: true}},
|
||||
Data: []PivotTableField{{Data: "Sales", Subtotal: "-", Name: strings.Repeat("s", MaxFieldLength+1)}},
|
||||
|
@ -228,8 +247,8 @@ func TestAddPivotTable(t *testing.T) {
|
|||
|
||||
// Test add pivot table with invalid sheet name
|
||||
assert.EqualError(t, f.AddPivotTable(&PivotTableOptions{
|
||||
DataRange: "Sheet:1!$A$1:$E$31",
|
||||
PivotTableRange: "Sheet:1!$G$2:$M$34",
|
||||
DataRange: "Sheet:1!A1:E31",
|
||||
PivotTableRange: "Sheet:1!G2:M34",
|
||||
Rows: []PivotTableField{{Data: "Year"}},
|
||||
}), ErrSheetNameInvalid.Error())
|
||||
// Test adjust range with invalid range
|
||||
|
@ -245,8 +264,8 @@ func TestAddPivotTable(t *testing.T) {
|
|||
assert.EqualError(t, f.addPivotCache("", &PivotTableOptions{}), "parameter 'DataRange' parsing error: parameter is required")
|
||||
// Test add pivot cache with invalid data range
|
||||
assert.EqualError(t, f.addPivotCache("", &PivotTableOptions{
|
||||
DataRange: "$A$1:$E$31",
|
||||
PivotTableRange: "Sheet1!$U$34:$O$2",
|
||||
DataRange: "A1:E31",
|
||||
PivotTableRange: "Sheet1!U34:O2",
|
||||
Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
|
||||
Columns: []PivotTableField{{Data: "Type", DefaultSubtotal: true}},
|
||||
Data: []PivotTableField{{Data: "Sales"}},
|
||||
|
@ -257,8 +276,8 @@ func TestAddPivotTable(t *testing.T) {
|
|||
assert.EqualError(t, f.addPivotTable(0, 0, "", &PivotTableOptions{}), "parameter 'PivotTableRange' parsing error: parameter is required")
|
||||
// Test add pivot fields with empty data range
|
||||
assert.EqualError(t, f.addPivotFields(nil, &PivotTableOptions{
|
||||
DataRange: "$A$1:$E$31",
|
||||
PivotTableRange: "Sheet1!$U$34:$O$2",
|
||||
DataRange: "A1:E31",
|
||||
PivotTableRange: "Sheet1!U34:O2",
|
||||
Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
|
||||
Columns: []PivotTableField{{Data: "Type", DefaultSubtotal: true}},
|
||||
Data: []PivotTableField{{Data: "Sales"}},
|
||||
|
@ -271,17 +290,53 @@ func TestAddPivotTable(t *testing.T) {
|
|||
f.ContentTypes = nil
|
||||
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
|
||||
assert.EqualError(t, f.AddPivotTable(&PivotTableOptions{
|
||||
DataRange: "Sheet1!$A$1:$E$31",
|
||||
PivotTableRange: "Sheet1!$G$2:$M$34",
|
||||
DataRange: "Sheet1!A1:E31",
|
||||
PivotTableRange: "Sheet1!G2:M34",
|
||||
Rows: []PivotTableField{{Data: "Year"}},
|
||||
}), "XML syntax error on line 1: invalid UTF-8")
|
||||
assert.NoError(t, f.Close())
|
||||
|
||||
// Test get pivot table without pivot table
|
||||
f = NewFile()
|
||||
pivotTables, err = f.GetPivotTables("Sheet1")
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, pivotTables, 0)
|
||||
// Test get pivot table with not exists worksheet
|
||||
_, err = f.GetPivotTables("SheetN")
|
||||
assert.EqualError(t, err, "sheet SheetN does not exist")
|
||||
// Test get pivot table with unsupported charset worksheet relationships
|
||||
f.Pkg.Store("xl/worksheets/_rels/sheet1.xml.rels", MacintoshCyrillicCharset)
|
||||
_, err = f.GetPivotTables("Sheet1")
|
||||
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
|
||||
assert.NoError(t, f.Close())
|
||||
// Test get pivot table with unsupported charset pivot cache definition
|
||||
f, err = OpenFile(filepath.Join("test", "TestAddPivotTable1.xlsx"))
|
||||
assert.NoError(t, err)
|
||||
f.Pkg.Store("xl/pivotCache/pivotCacheDefinition1.xml", MacintoshCyrillicCharset)
|
||||
_, err = f.GetPivotTables("Sheet1")
|
||||
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
|
||||
assert.NoError(t, f.Close())
|
||||
// Test get pivot table with unsupported charset pivot table relationships
|
||||
f, err = OpenFile(filepath.Join("test", "TestAddPivotTable1.xlsx"))
|
||||
assert.NoError(t, err)
|
||||
f.Pkg.Store("xl/pivotTables/_rels/pivotTable1.xml.rels", MacintoshCyrillicCharset)
|
||||
_, err = f.GetPivotTables("Sheet1")
|
||||
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
|
||||
assert.NoError(t, f.Close())
|
||||
// Test get pivot table with unsupported charset pivot table
|
||||
f, err = OpenFile(filepath.Join("test", "TestAddPivotTable1.xlsx"))
|
||||
assert.NoError(t, err)
|
||||
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")
|
||||
assert.NoError(t, f.Close())
|
||||
}
|
||||
|
||||
func TestAddPivotRowFields(t *testing.T) {
|
||||
f := NewFile()
|
||||
// Test invalid data range
|
||||
assert.EqualError(t, f.addPivotRowFields(&xlsxPivotTableDefinition{}, &PivotTableOptions{
|
||||
DataRange: "Sheet1!$A$1:$A$1",
|
||||
DataRange: "Sheet1!A1:A1",
|
||||
}), `parameter 'DataRange' parsing error: parameter is invalid`)
|
||||
}
|
||||
|
||||
|
@ -289,7 +344,7 @@ func TestAddPivotPageFields(t *testing.T) {
|
|||
f := NewFile()
|
||||
// Test invalid data range
|
||||
assert.EqualError(t, f.addPivotPageFields(&xlsxPivotTableDefinition{}, &PivotTableOptions{
|
||||
DataRange: "Sheet1!$A$1:$A$1",
|
||||
DataRange: "Sheet1!A1:A1",
|
||||
}), `parameter 'DataRange' parsing error: parameter is invalid`)
|
||||
}
|
||||
|
||||
|
@ -297,7 +352,7 @@ func TestAddPivotDataFields(t *testing.T) {
|
|||
f := NewFile()
|
||||
// Test invalid data range
|
||||
assert.EqualError(t, f.addPivotDataFields(&xlsxPivotTableDefinition{}, &PivotTableOptions{
|
||||
DataRange: "Sheet1!$A$1:$A$1",
|
||||
DataRange: "Sheet1!A1:A1",
|
||||
}), `parameter 'DataRange' parsing error: parameter is invalid`)
|
||||
}
|
||||
|
||||
|
@ -305,7 +360,7 @@ func TestAddPivotColFields(t *testing.T) {
|
|||
f := NewFile()
|
||||
// Test invalid data range
|
||||
assert.EqualError(t, f.addPivotColFields(&xlsxPivotTableDefinition{}, &PivotTableOptions{
|
||||
DataRange: "Sheet1!$A$1:$A$1",
|
||||
DataRange: "Sheet1!A1:A1",
|
||||
Columns: []PivotTableField{{Data: "Type", DefaultSubtotal: true}},
|
||||
}), `parameter 'DataRange' parsing error: parameter is invalid`)
|
||||
}
|
||||
|
@ -313,7 +368,7 @@ func TestAddPivotColFields(t *testing.T) {
|
|||
func TestGetPivotFieldsOrder(t *testing.T) {
|
||||
f := NewFile()
|
||||
// Test get pivot fields order with not exist worksheet
|
||||
_, err := f.getPivotFieldsOrder(&PivotTableOptions{DataRange: "SheetN!$A$1:$E$31"})
|
||||
_, err := f.getPivotFieldsOrder(&PivotTableOptions{DataRange: "SheetN!A1:E31"})
|
||||
assert.EqualError(t, err, "sheet SheetN does not exist")
|
||||
}
|
||||
|
||||
|
|
2
sheet.go
2
sheet.go
|
@ -1595,7 +1595,7 @@ func (f *File) SetDefinedName(definedName *DefinedName) error {
|
|||
if definedName.Name == "" || definedName.RefersTo == "" {
|
||||
return ErrParameterInvalid
|
||||
}
|
||||
if err := checkDefinedName(definedName.Name); err != nil {
|
||||
if err := checkDefinedName(definedName.Name); err != nil && inStrSlice(builtInDefinedNames[:2], definedName.Name, false) == -1 {
|
||||
return err
|
||||
}
|
||||
wb, err := f.workbookReader()
|
||||
|
|
|
@ -276,6 +276,16 @@ func TestDefinedName(t *testing.T) {
|
|||
RefersTo: "Sheet1!$A$2:$D$5",
|
||||
Comment: "defined name comment",
|
||||
}))
|
||||
assert.NoError(t, f.SetDefinedName(&DefinedName{
|
||||
Name: builtInDefinedNames[0],
|
||||
RefersTo: "Sheet1!$A$1:$Z$100",
|
||||
Scope: "Sheet1",
|
||||
}))
|
||||
assert.NoError(t, f.SetDefinedName(&DefinedName{
|
||||
Name: builtInDefinedNames[1],
|
||||
RefersTo: "Sheet1!$A:$A,Sheet1!$1:$1",
|
||||
Scope: "Sheet1",
|
||||
}))
|
||||
assert.EqualError(t, f.SetDefinedName(&DefinedName{
|
||||
Name: "Amount",
|
||||
RefersTo: "Sheet1!$A$2:$D$5",
|
||||
|
@ -297,7 +307,7 @@ func TestDefinedName(t *testing.T) {
|
|||
Name: "Amount",
|
||||
}))
|
||||
assert.Exactly(t, "Sheet1!$A$2:$D$5", f.GetDefinedName()[0].RefersTo)
|
||||
assert.Len(t, f.GetDefinedName(), 1)
|
||||
assert.Len(t, f.GetDefinedName(), 3)
|
||||
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDefinedName.xlsx")))
|
||||
// Test set defined name with unsupported charset workbook
|
||||
f.WorkBook = nil
|
||||
|
|
|
@ -267,16 +267,14 @@ func TestAddSparkline(t *testing.T) {
|
|||
// Test creating a conditional format with existing extension lists
|
||||
ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
|
||||
assert.True(t, ok)
|
||||
ws.(*xlsxWorksheet).ExtLst = &xlsxExtLst{Ext: `
|
||||
<ext uri="{A8765BA9-456A-4dab-B4F3-ACF838C121DE}"><x14:slicerList /></ext>
|
||||
<ext uri="{05C60535-1F16-4fd2-B633-F4F36F0B64E0}"><x14:sparklineGroups /></ext>`}
|
||||
ws.(*xlsxWorksheet).ExtLst = &xlsxExtLst{Ext: fmt.Sprintf(`<ext uri="%s"><x14:slicerList /></ext><ext uri="%s"><x14:sparklineGroups /></ext>`, ExtURISlicerListX14, ExtURISparklineGroups)}
|
||||
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{
|
||||
Location: []string{"A3"},
|
||||
Range: []string{"Sheet3!A2:J2"},
|
||||
Type: "column",
|
||||
}))
|
||||
// Test creating a conditional format with invalid extension list characters
|
||||
ws.(*xlsxWorksheet).ExtLst.Ext = `<ext uri="{05C60535-1F16-4fd2-B633-F4F36F0B64E0}"><x14:sparklineGroups><x14:sparklineGroup></x14:sparklines></x14:sparklineGroup></x14:sparklineGroups></ext>`
|
||||
ws.(*xlsxWorksheet).ExtLst.Ext = fmt.Sprintf(`<ext uri="%s"><x14:sparklineGroups><x14:sparklineGroup></x14:sparklines></x14:sparklineGroup></x14:sparklineGroups></ext>`, ExtURISparklineGroups)
|
||||
assert.EqualError(t, f.AddSparkline("Sheet1", &SparklineOptions{
|
||||
Location: []string{"A2"},
|
||||
Range: []string{"Sheet3!A1:J1"},
|
||||
|
|
|
@ -1127,7 +1127,7 @@ func (f *File) getThemeColor(clr *xlsxColor) string {
|
|||
if len(clr.RGB) == 8 {
|
||||
return strings.TrimPrefix(clr.RGB, "FF")
|
||||
}
|
||||
if f.Styles.Colors != nil && clr.Indexed < len(f.Styles.Colors.IndexedColors.RgbColor) {
|
||||
if f.Styles.Colors != nil && f.Styles.Colors.IndexedColors != nil && clr.Indexed < len(f.Styles.Colors.IndexedColors.RgbColor) {
|
||||
return strings.TrimPrefix(ThemeColor(strings.TrimPrefix(f.Styles.Colors.IndexedColors.RgbColor[clr.Indexed].RGB, "FF"), clr.Tint), "FF")
|
||||
}
|
||||
if clr.Indexed < len(IndexedColorMapping) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package excelize
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
@ -180,9 +181,7 @@ func TestSetConditionalFormat(t *testing.T) {
|
|||
// Test creating a conditional format with existing extension lists
|
||||
ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
|
||||
assert.True(t, ok)
|
||||
ws.(*xlsxWorksheet).ExtLst = &xlsxExtLst{Ext: `
|
||||
<ext uri="{A8765BA9-456A-4dab-B4F3-ACF838C121DE}"><x14:slicerList /></ext>
|
||||
<ext uri="{05C60535-1F16-4fd2-B633-F4F36F0B64E0}"><x14:sparklineGroups /></ext>`}
|
||||
ws.(*xlsxWorksheet).ExtLst = &xlsxExtLst{Ext: fmt.Sprintf(`<ext uri="%s"><x14:slicerList /></ext><ext uri="%s"><x14:sparklineGroups /></ext>`, ExtURISlicerListX14, ExtURISparklineGroups)}
|
||||
assert.NoError(t, f.SetConditionalFormat("Sheet1", "A1:A2", []ConditionalFormatOptions{{Type: "data_bar", Criteria: "=", MinType: "min", MaxType: "max", BarBorderColor: "#0000FF", BarColor: "#638EC6", BarSolid: true}}))
|
||||
f = NewFile()
|
||||
// Test creating a conditional format with invalid extension list characters
|
||||
|
@ -573,7 +572,7 @@ func TestGetStyle(t *testing.T) {
|
|||
|
||||
// Test get style with custom color index
|
||||
f.Styles.Colors = &xlsxStyleColors{
|
||||
IndexedColors: xlsxIndexedColors{
|
||||
IndexedColors: &xlsxIndexedColors{
|
||||
RgbColor: []xlsxColor{{RGB: "FF012345"}},
|
||||
},
|
||||
}
|
||||
|
|
5
table.go
5
table.go
|
@ -427,7 +427,6 @@ 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)
|
||||
filterDB := "_xlnm._FilterDatabase"
|
||||
wb, err := f.workbookReader()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -438,7 +437,7 @@ func (f *File) AutoFilter(sheet, rangeRef string, opts []AutoFilterOptions) erro
|
|||
}
|
||||
filterRange := fmt.Sprintf("'%s'!%s", sheet, ref)
|
||||
d := xlsxDefinedName{
|
||||
Name: filterDB,
|
||||
Name: builtInDefinedNames[2],
|
||||
Hidden: true,
|
||||
LocalSheetID: intPtr(sheetID),
|
||||
Data: filterRange,
|
||||
|
@ -451,7 +450,7 @@ func (f *File) AutoFilter(sheet, rangeRef string, opts []AutoFilterOptions) erro
|
|||
var definedNameExists bool
|
||||
for idx := range wb.DefinedNames.DefinedName {
|
||||
definedName := wb.DefinedNames.DefinedName[idx]
|
||||
if definedName.Name == filterDB && *definedName.LocalSheetID == sheetID && definedName.Hidden {
|
||||
if definedName.Name == builtInDefinedNames[2] && *definedName.LocalSheetID == sheetID && definedName.Hidden {
|
||||
wb.DefinedNames.DefinedName[idx].Data = filterRange
|
||||
definedNameExists = true
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ var (
|
|||
NameSpaceSpreadSheetExcel2006Main = xml.Attr{Name: xml.Name{Local: "xne", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/excel/2006/main"}
|
||||
NameSpaceSpreadSheetX14 = xml.Attr{Name: xml.Name{Local: "x14", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main"}
|
||||
NameSpaceSpreadSheetX15 = xml.Attr{Name: xml.Name{Local: "x15", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/spreadsheetml/2010/11/main"}
|
||||
NameSpaceSpreadSheetXR10 = xml.Attr{Name: xml.Name{Local: "xr10", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/spreadsheetml/2016/revision10"}
|
||||
SourceRelationship = xml.Attr{Name: xml.Name{Local: "r", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/officeDocument/2006/relationships"}
|
||||
SourceRelationshipChart20070802 = xml.Attr{Name: xml.Name{Local: "c14", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/drawing/2007/8/2/chart"}
|
||||
SourceRelationshipChart2014 = xml.Attr{Name: xml.Name{Local: "c16", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/drawing/2014/chart"}
|
||||
|
@ -43,6 +44,8 @@ const (
|
|||
ContentTypeDrawingML = "application/vnd.openxmlformats-officedocument.drawingml.chart+xml"
|
||||
ContentTypeMacro = "application/vnd.ms-excel.sheet.macroEnabled.main+xml"
|
||||
ContentTypeSheetML = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"
|
||||
ContentTypeSlicer = "application/vnd.ms-excel.slicer+xml"
|
||||
ContentTypeSlicerCache = "application/vnd.ms-excel.slicerCache+xml"
|
||||
ContentTypeSpreadSheetMLChartsheet = "application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml"
|
||||
ContentTypeSpreadSheetMLComments = "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml"
|
||||
ContentTypeSpreadSheetMLPivotCacheDefinition = "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml"
|
||||
|
@ -74,6 +77,7 @@ const (
|
|||
SourceRelationshipPivotCache = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheDefinition"
|
||||
SourceRelationshipPivotTable = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotTable"
|
||||
SourceRelationshipSharedStrings = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings"
|
||||
SourceRelationshipSlicer = "http://schemas.microsoft.com/office/2007/relationships/slicer"
|
||||
SourceRelationshipTable = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/table"
|
||||
SourceRelationshipVBAProject = "http://schemas.microsoft.com/office/2006/relationships/vbaProject"
|
||||
SourceRelationshipWorkSheet = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"
|
||||
|
@ -97,6 +101,7 @@ const (
|
|||
ExtURIDrawingBlip = "{28A0092B-C50C-407E-A947-70E740481C1C}"
|
||||
ExtURIIgnoredErrors = "{01252117-D84E-4E92-8308-4BE1C098FCBB}"
|
||||
ExtURIMacExcelMX = "{64002731-A6B0-56B0-2670-7721B7C09600}"
|
||||
ExtURIPivotCacheDefinition = "{725AE2AE-9491-48be-B2B4-4EB974FC3084}"
|
||||
ExtURIProtectedRanges = "{FC87AEE6-9EDD-4A0A-B7FB-166176984837}"
|
||||
ExtURISlicerCachesListX14 = "{BBE1A952-AA13-448e-AADC-164F8A28A991}"
|
||||
ExtURISlicerListX14 = "{A8765BA9-456A-4DAB-B4F3-ACF838C121DE}"
|
||||
|
@ -222,6 +227,9 @@ var supportedDrawingUnderlineTypes = []string{
|
|||
// supportedPositioning defined supported positioning types.
|
||||
var supportedPositioning = []string{"absolute", "oneCell", "twoCell"}
|
||||
|
||||
// builtInDefinedNames defined built-in defined names are built with a _xlnm prefix.
|
||||
var builtInDefinedNames = []string{"_xlnm.Print_Area", "_xlnm.Print_Titles", "_xlnm._FilterDatabase"}
|
||||
|
||||
// xlsxCNvPr directly maps the cNvPr (Non-Visual Drawing Properties). This
|
||||
// element specifies non-visual canvas properties. This allows for additional
|
||||
// information that does not affect the appearance of the picture to be stored.
|
||||
|
|
|
@ -311,8 +311,8 @@ type xlsxIndexedColors struct {
|
|||
// legacy color palette has been modified (backwards compatibility settings) or
|
||||
// a custom color has been selected while using this workbook.
|
||||
type xlsxStyleColors struct {
|
||||
IndexedColors xlsxIndexedColors `xml:"indexedColors"`
|
||||
MruColors xlsxInnerXML `xml:"mruColors"`
|
||||
IndexedColors *xlsxIndexedColors `xml:"indexedColors"`
|
||||
MruColors xlsxInnerXML `xml:"mruColors"`
|
||||
}
|
||||
|
||||
// Alignment directly maps the alignment settings of the cells.
|
||||
|
|
Loading…
Reference in New Issue