Skip to content

Commit 4e1eb8b

Browse files
author
mengzhongyuan
committed
This close qax-os#2146, support setting excel custom properties (properties in DocProps/custom.xml)
- support save some biz k:v data to excel custom properties to do such that save excel biz version etc. Change-Id: I4a524b494f09fbfca321d515b5029f4f7361311f Signed-off-by: mengzhongyuan <mengzhongyuan@bytedance.com>
1 parent 5bd9647 commit 4e1eb8b

14 files changed

+317
-63
lines changed

calc.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12073,7 +12073,7 @@ func (fn *formulaFuncs) XOR(argsList *list.List) formulaArg {
1207312073
return calcXor(argsList)
1207412074
}
1207512075

12076-
// Date and Time Functions
12076+
// Date and DateTime Functions
1207712077

1207812078
// DATE returns a date, from a user-supplied year, month and day. The syntax
1207912079
// of the function is:

calc_test.go

Lines changed: 51 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -777,25 +777,25 @@ func TestCalcCellValue(t *testing.T) {
777777
"=ROUNDUP(-11.111,-1)": "-20",
778778
"=ROUNDUP(ROUNDUP(100,1),-1)": "100",
779779
// SEARCH
780-
"=SEARCH(\"s\",F1)": "1",
781-
"=SEARCH(\"s\",F1,2)": "5",
782-
"=SEARCH(\"e\",F1)": "4",
783-
"=SEARCH(\"e*\",F1)": "4",
784-
"=SEARCH(\"?e\",F1)": "3",
785-
"=SEARCH(\"??e\",F1)": "2",
786-
"=SEARCH(6,F2)": "2",
780+
"=SEARCH(\"s\",F1)": "1",
781+
"=SEARCH(\"s\",F1,2)": "5",
782+
"=SEARCH(\"e\",F1)": "4",
783+
"=SEARCH(\"e*\",F1)": "4",
784+
"=SEARCH(\"?e\",F1)": "3",
785+
"=SEARCH(\"??e\",F1)": "2",
786+
"=SEARCH(6,F2)": "2",
787787
"=SEARCH(\"?\",\"你好world\")": "1",
788788
"=SEARCH(\"?l\",\"你好world\")": "5",
789789
"=SEARCH(\"?+\",\"你好 1+2\")": "4",
790790
"=SEARCH(\" ?+\",\"你好 1+2\")": "3",
791791
// SEARCHB
792-
"=SEARCHB(\"s\",F1)": "1",
793-
"=SEARCHB(\"s\",F1,2)": "5",
794-
"=SEARCHB(\"e\",F1)": "4",
795-
"=SEARCHB(\"e*\",F1)": "4",
796-
"=SEARCHB(\"?e\",F1)": "3",
797-
"=SEARCHB(\"??e\",F1)": "2",
798-
"=SEARCHB(6,F2)": "2",
792+
"=SEARCHB(\"s\",F1)": "1",
793+
"=SEARCHB(\"s\",F1,2)": "5",
794+
"=SEARCHB(\"e\",F1)": "4",
795+
"=SEARCHB(\"e*\",F1)": "4",
796+
"=SEARCHB(\"?e\",F1)": "3",
797+
"=SEARCHB(\"??e\",F1)": "2",
798+
"=SEARCHB(6,F2)": "2",
799799
"=SEARCHB(\"?\",\"你好world\")": "5",
800800
"=SEARCHB(\"?l\",\"你好world\")": "7",
801801
"=SEARCHB(\"?+\",\"你好 1+2\")": "6",
@@ -1541,7 +1541,7 @@ func TestCalcCellValue(t *testing.T) {
15411541
"=XOR(1>0,2>0)": "FALSE",
15421542
"=XOR(1>0,0>1)": "TRUE",
15431543
"=XOR(1>0,0>1,INT(0),INT(1),A1:A4,2)": "FALSE",
1544-
// Date and Time Functions
1544+
// Date and DateTime Functions
15451545
// DATE
15461546
"=DATE(2020,10,21)": "44125",
15471547
"=DATE(2020,10,21)+1": "44126",
@@ -1764,33 +1764,33 @@ func TestCalcCellValue(t *testing.T) {
17641764
"=FINDB(\"\",\"Original Text\",2)": "2",
17651765
"=FINDB(\"s\",\"Sales\",2)": "5",
17661766
// LEFT
1767-
"=LEFT(\"Original Text\")": "O",
1768-
"=LEFT(\"Original Text\",4)": "Orig",
1769-
"=LEFT(\"Original Text\",0)": "",
1770-
"=LEFT(\"Original Text\",13)": "Original Text",
1771-
"=LEFT(\"Original Text\",20)": "Original Text",
1772-
"=LEFT(\"オリジナルテキスト\")": "オ",
1773-
"=LEFT(\"オリジナルテキスト\",2)": "オリ",
1774-
"=LEFT(\"オリジナルテキスト\",5)": "オリジナル",
1775-
"=LEFT(\"オリジナルテキスト\",7)": "オリジナルテキ",
1776-
"=LEFT(\"オリジナルテキスト\",20)": "オリジナルテキスト",
1767+
"=LEFT(\"Original Text\")": "O",
1768+
"=LEFT(\"Original Text\",4)": "Orig",
1769+
"=LEFT(\"Original Text\",0)": "",
1770+
"=LEFT(\"Original Text\",13)": "Original Text",
1771+
"=LEFT(\"Original Text\",20)": "Original Text",
1772+
"=LEFT(\"オリジナルテキスト\")": "オ",
1773+
"=LEFT(\"オリジナルテキスト\",2)": "オリ",
1774+
"=LEFT(\"オリジナルテキスト\",5)": "オリジナル",
1775+
"=LEFT(\"オリジナルテキスト\",7)": "オリジナルテキ",
1776+
"=LEFT(\"オリジナルテキスト\",20)": "オリジナルテキスト",
17771777
// LEFTB
17781778
"=LEFTB(\"Original Text\")": "O",
17791779
"=LEFTB(\"Original Text\",4)": "Orig",
17801780
"=LEFTB(\"Original Text\",0)": "",
17811781
"=LEFTB(\"Original Text\",13)": "Original Text",
17821782
"=LEFTB(\"Original Text\",20)": "Original Text",
17831783
// LEN
1784-
"=LEN(\"\")": "0",
1785-
"=LEN(D1)": "5",
1786-
"=LEN(\"テキスト\")": "4",
1787-
"=LEN(\"オリジナルテキスト\")": "9",
1788-
"=LEN(7+LEN(A1&B1&C1))": "1",
1789-
"=LEN(8+LEN(A1+(C1-B1)))": "2",
1784+
"=LEN(\"\")": "0",
1785+
"=LEN(D1)": "5",
1786+
"=LEN(\"テキスト\")": "4",
1787+
"=LEN(\"オリジナルテキスト\")": "9",
1788+
"=LEN(7+LEN(A1&B1&C1))": "1",
1789+
"=LEN(8+LEN(A1+(C1-B1)))": "2",
17901790
// LENB
1791-
"=LENB(\"\")": "0",
1792-
"=LENB(D1)": "5",
1793-
"=LENB(\"テキスト\")": "8",
1791+
"=LENB(\"\")": "0",
1792+
"=LENB(D1)": "5",
1793+
"=LENB(\"テキスト\")": "8",
17941794
"=LENB(\"オリジナルテキスト\")": "18",
17951795
// LOWER
17961796
"=LOWER(\"test\")": "test",
@@ -1803,7 +1803,7 @@ func TestCalcCellValue(t *testing.T) {
18031803
"=MID(\"255 years\",3,1)": "5",
18041804
"=MID(\"text\",3,6)": "xt",
18051805
"=MID(\"text\",6,0)": "",
1806-
"=MID(\"你好World\",5,1)": "r",
1806+
"=MID(\"你好World\",5,1)": "r",
18071807
"=MID(\"\u30AA\u30EA\u30B8\u30CA\u30EB\u30C6\u30AD\u30B9\u30C8\",6,4)": "\u30C6\u30AD\u30B9\u30C8",
18081808
"=MID(\"\u30AA\u30EA\u30B8\u30CA\u30EB\u30C6\u30AD\u30B9\u30C8\",3,5)": "\u30B8\u30CA\u30EB\u30C6\u30AD",
18091809
// MIDB
@@ -1812,7 +1812,7 @@ func TestCalcCellValue(t *testing.T) {
18121812
"=MIDB(\"255 years\",3,1)": "5",
18131813
"=MIDB(\"text\",3,6)": "xt",
18141814
"=MIDB(\"text\",6,0)": "",
1815-
"=MIDB(\"你好World\",5,1)": "W",
1815+
"=MIDB(\"你好World\",5,1)": "W",
18161816
"=MIDB(\"\u30AA\u30EA\u30B8\u30CA\u30EB\u30C6\u30AD\u30B9\u30C8\",6,4)": "\u30B8\u30CA",
18171817
"=MIDB(\"\u30AA\u30EA\u30B8\u30CA\u30EB\u30C6\u30AD\u30B9\u30C8\",3,5)": "\u30EA\u30B8\xe3",
18181818
// PROPER
@@ -1835,16 +1835,16 @@ func TestCalcCellValue(t *testing.T) {
18351835
"=REPT(\"*\",1)": "*",
18361836
"=REPT(\"**\",2)": "****",
18371837
// RIGHT
1838-
"=RIGHT(\"Original Text\")": "t",
1839-
"=RIGHT(\"Original Text\",4)": "Text",
1840-
"=RIGHT(\"Original Text\",0)": "",
1841-
"=RIGHT(\"Original Text\",13)": "Original Text",
1842-
"=RIGHT(\"Original Text\",20)": "Original Text",
1843-
"=RIGHT(\"オリジナルテキスト\")": "ト",
1844-
"=RIGHT(\"オリジナルテキスト\",2)": "スト",
1845-
"=RIGHT(\"オリジナルテキスト\",4)": "テキスト",
1846-
"=RIGHT(\"オリジナルテキスト\",7)": "ジナルテキスト",
1847-
"=RIGHT(\"オリジナルテキスト\",20)": "オリジナルテキスト",
1838+
"=RIGHT(\"Original Text\")": "t",
1839+
"=RIGHT(\"Original Text\",4)": "Text",
1840+
"=RIGHT(\"Original Text\",0)": "",
1841+
"=RIGHT(\"Original Text\",13)": "Original Text",
1842+
"=RIGHT(\"Original Text\",20)": "Original Text",
1843+
"=RIGHT(\"オリジナルテキスト\")": "ト",
1844+
"=RIGHT(\"オリジナルテキスト\",2)": "スト",
1845+
"=RIGHT(\"オリジナルテキスト\",4)": "テキスト",
1846+
"=RIGHT(\"オリジナルテキスト\",7)": "ジナルテキスト",
1847+
"=RIGHT(\"オリジナルテキスト\",20)": "オリジナルテキスト",
18481848
// RIGHTB
18491849
"=RIGHTB(\"Original Text\")": "t",
18501850
"=RIGHTB(\"Original Text\",4)": "Text",
@@ -2806,11 +2806,11 @@ func TestCalcCellValue(t *testing.T) {
28062806
"=SEARCH(2,A1)": {"#VALUE!", "#VALUE!"},
28072807
"=SEARCH(1,A1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
28082808
// SEARCHB
2809-
"=SEARCHB()": {"#VALUE!", "SEARCHB requires at least 2 arguments"},
2810-
"=SEARCHB(1,A1,1,1)": {"#VALUE!", "SEARCHB allows at most 3 arguments"},
2811-
"=SEARCHB(2,A1)": {"#VALUE!", "#VALUE!"},
2809+
"=SEARCHB()": {"#VALUE!", "SEARCHB requires at least 2 arguments"},
2810+
"=SEARCHB(1,A1,1,1)": {"#VALUE!", "SEARCHB allows at most 3 arguments"},
2811+
"=SEARCHB(2,A1)": {"#VALUE!", "#VALUE!"},
28122812
"=SEARCHB(\"?w\",\"你好world\")": {"#VALUE!", "#VALUE!"},
2813-
"=SEARCHB(1,A1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
2813+
"=SEARCHB(1,A1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
28142814
// SEC
28152815
"=_xlfn.SEC()": {"#VALUE!", "SEC requires 1 numeric argument"},
28162816
"=_xlfn.SEC(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
@@ -3693,7 +3693,7 @@ func TestCalcCellValue(t *testing.T) {
36933693
"=XOR(\"1\")": {"#VALUE!", "#VALUE!"},
36943694
"=XOR(\"text\")": {"#VALUE!", "#VALUE!"},
36953695
"=XOR(XOR(\"text\"))": {"#VALUE!", "#VALUE!"},
3696-
// Date and Time Functions
3696+
// Date and DateTime Functions
36973697
// DATE
36983698
"=DATE()": {"#VALUE!", "DATE requires 3 number arguments"},
36993699
"=DATE(\"text\",10,21)": {"#VALUE!", "DATE requires 3 number arguments"},

cell.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,14 +117,14 @@ func (f *File) GetCellType(sheet, cell string) (CellType, error) {
117117
// string
118118
// []byte
119119
// time.Duration
120-
// time.Time
120+
// time.DateTime
121121
// bool
122122
// nil
123123
//
124-
// Note that default date format is m/d/yy h:mm of time.Time type value. You
124+
// Note that default date format is m/d/yy h:mm of time.DateTime type value. You
125125
// can set numbers format by the SetCellStyle function. If you need to set the
126126
// specialized date in Excel like January 0, 1900 or February 29, 1900, these
127-
// times can not representation in Go language time.Time data type. Please set
127+
// times can not representation in Go language time.DateTime data type. Please set
128128
// the cell value as number 0 or 60, then create and bind the date-time number
129129
// format style for the cell.
130130
func (f *File) SetCellValue(sheet, cell string, value interface{}) error {
@@ -264,7 +264,7 @@ func (f *File) setCellTimeFunc(sheet, cell string, value time.Time) error {
264264
return err
265265
}
266266

267-
// setCellTime prepares cell type and Excel time by given Go time.Time type
267+
// setCellTime prepares cell type and Excel time by given Go time.DateTime type
268268
// timestamp.
269269
func (c *xlsxC) setCellTime(value time.Time, date1904 bool) (isNum bool, err error) {
270270
var excelTime float64

date.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ func doTheFliegelAndVanFlandernAlgorithm(jd int) (day, month, year int) {
131131
}
132132

133133
// timeFromExcelTime provides a function to convert an excelTime
134-
// representation (stored as a floating point number) to a time.Time.
134+
// representation (stored as a floating point number) to a time.DateTime.
135135
func timeFromExcelTime(excelTime float64, date1904 bool) time.Time {
136136
var date time.Time
137137
wholeDaysPart := int(excelTime)
@@ -163,7 +163,7 @@ func timeFromExcelTime(excelTime float64, date1904 bool) time.Time {
163163
return date.Truncate(time.Second)
164164
}
165165

166-
// ExcelDateToTime converts a float-based Excel date representation to a time.Time.
166+
// ExcelDateToTime converts a float-based Excel date representation to a time.DateTime.
167167
func ExcelDateToTime(excelDate float64, use1904Format bool) (time.Time, error) {
168168
if excelDate < 0 {
169169
return time.Time{}, newInvalidExcelDateError(excelDate)

date_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ func TestTimeToExcelTime(t *testing.T) {
4343
excelTime, err := timeToExcelTime(test.GoValue, false)
4444
assert.NoError(t, err)
4545
assert.Equalf(t, test.ExcelValue, excelTime,
46-
"Time: %s", test.GoValue.String())
46+
"DateTime: %s", test.GoValue.String())
4747
})
4848
}
4949
}

docProps.go

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import (
1616
"encoding/xml"
1717
"io"
1818
"reflect"
19+
"strconv"
20+
"time"
1921
)
2022

2123
// SetAppProps provides a function to set document application properties. The
@@ -206,6 +208,123 @@ func (f *File) SetDocProps(docProperties *DocProperties) error {
206208
return err
207209
}
208210

211+
// SetDocCustomProps provides a function to set excel custom properties, support string, bool, float64,
212+
// time.DateTime four types.
213+
func (f *File) SetDocCustomProps(name string, value interface{}) error {
214+
customProps := new(xlsxCustomProperties)
215+
216+
// find existing custom properties
217+
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathDocPropsCustom)))).
218+
Decode(customProps); err != nil && err != io.EOF {
219+
return err
220+
}
221+
222+
props := customProps.Props
223+
existingPropertyMap := make(map[string]xlsxCustomProperty)
224+
maxPID := 1 // pid from 2
225+
for _, prop := range props {
226+
pid, err := strconv.Atoi(prop.PID)
227+
if err == nil && pid > maxPID {
228+
maxPID = pid
229+
}
230+
231+
existingPropertyMap[prop.Name] = prop
232+
}
233+
234+
// different custom property value type setter function
235+
var setValueFunc func(*xlsxCustomProperty) error
236+
switch v := value.(type) {
237+
case float64:
238+
setValueFunc = func(property *xlsxCustomProperty) error {
239+
property.Number = &NumberValue{Number: v}
240+
return nil
241+
}
242+
case bool:
243+
setValueFunc = func(property *xlsxCustomProperty) error {
244+
property.Bool = &BoolValue{Bool: v}
245+
return nil
246+
}
247+
case string:
248+
setValueFunc = func(property *xlsxCustomProperty) error {
249+
property.Text = &TextValue{Text: v}
250+
return nil
251+
}
252+
case time.Time:
253+
setValueFunc = func(property *xlsxCustomProperty) error {
254+
property.DateTime = &FileTimeValue{
255+
DateTime: v.Format(time.RFC3339),
256+
}
257+
return nil
258+
}
259+
default:
260+
setValueFunc = func(_ *xlsxCustomProperty) error {
261+
return ErrUnsupportedCustomPropertyDataType
262+
}
263+
}
264+
265+
// update existing custom properties
266+
if existingProperty, ok := existingPropertyMap[name]; ok {
267+
if err := setValueFunc(&existingProperty); err != nil {
268+
return err
269+
}
270+
} else {
271+
// add new custom property
272+
newProperty := xlsxCustomProperty{
273+
FmtID: CustomPropertiesFMTID,
274+
PID: strconv.FormatInt(int64(maxPID+1), 10), // max pid plus 1 to create a new unique pid
275+
Name: name,
276+
}
277+
278+
if err := setValueFunc(&newProperty); err != nil {
279+
return err
280+
}
281+
282+
props = append(props, newProperty)
283+
}
284+
285+
newCustomProps := &xlsxCustomProperties{
286+
Vt: NameSpaceDocumentPropertiesVariantTypes.Value,
287+
Props: props,
288+
}
289+
290+
output, err := xml.Marshal(newCustomProps)
291+
f.saveFileList(defaultXMLPathDocPropsCustom, output)
292+
293+
// set custom properties if necessary
294+
_ = f.addRels(defaultXMLRels, SourceRelationshipCustomProperties, defaultXMLPathDocPropsCustom, "")
295+
296+
// set content type if necessary
297+
_ = f.setContentTypes("/"+defaultXMLPathDocPropsCustom, ContentTypeCustomProperties)
298+
299+
return err
300+
}
301+
302+
// GetDocCustomProps provides a function to get document custom properties, supported string, bool, float64,
303+
// time.DateTime four types. If the custom property is not set, it will return an empty map, and if the custom property
304+
// value is invalid format, it returns as if the custom property does not exist.
305+
func (f *File) GetDocCustomProps() (kv map[string]interface{}, err error) {
306+
custom := new(xlsxCustomProperties)
307+
308+
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathDocPropsCustom)))).
309+
Decode(custom); err != nil && err != io.EOF {
310+
return
311+
}
312+
313+
kv = make(map[string]interface{})
314+
if custom == nil || len(custom.Props) == 0 {
315+
return kv, nil
316+
}
317+
318+
for _, prop := range custom.Props {
319+
propertyValue := prop.getPropertyValue()
320+
if propertyValue != nil {
321+
kv[prop.Name] = propertyValue
322+
}
323+
}
324+
325+
return kv, nil
326+
}
327+
209328
// GetDocProps provides a function to get document core properties.
210329
func (f *File) GetDocProps() (ret *DocProperties, err error) {
211330
core := new(decodeCoreProperties)

0 commit comments

Comments
 (0)