@@ -16,7 +16,7 @@ import (
16
16
"encoding/xml"
17
17
"io"
18
18
"reflect"
19
- "strconv "
19
+ "slices "
20
20
"time"
21
21
)
22
22
@@ -208,123 +208,6 @@ func (f *File) SetDocProps(docProperties *DocProperties) error {
208
208
return err
209
209
}
210
210
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
-
328
211
// GetDocProps provides a function to get document core properties.
329
212
func (f * File ) GetDocProps () (ret * DocProperties , err error ) {
330
213
core := new (decodeCoreProperties )
@@ -355,3 +238,117 @@ func (f *File) GetDocProps() (ret *DocProperties, err error) {
355
238
}
356
239
return
357
240
}
241
+
242
+ // SetCustomProps provides a function to set custom file properties by given
243
+ // property name and value. If the property name already exists, it will be
244
+ // updated, otherwise a new property will be added. The value can be of type
245
+ // int32, float64, bool, string, time.Time or nil. The property will be delete
246
+ // if the value is nil. The function returns an error if the property value is
247
+ // not of the correct type.
248
+ func (f * File ) SetCustomProps (prop CustomProperty ) error {
249
+ if prop .Name == "" {
250
+ return ErrParameterInvalid
251
+ }
252
+ props := new (decodeCustomProperties )
253
+ if err := f .xmlNewDecoder (bytes .NewReader (namespaceStrictToTransitional (f .readXML (defaultXMLPathDocPropsCustom )))).
254
+ Decode (props ); err != nil && err != io .EOF {
255
+ return err
256
+ }
257
+ customProps := xlsxCustomProperties {Vt : NameSpaceDocumentPropertiesVariantTypes .Value }
258
+ idx , pID := - 1 , 1
259
+ for i := range props .Property {
260
+ p := new (xlsxProperty )
261
+ setPtrFields (reflect .ValueOf (& props .Property [i ]).Elem (), reflect .ValueOf (p ).Elem ())
262
+ if pID < props .Property [i ].PID {
263
+ pID = props .Property [i ].PID
264
+ }
265
+ if props .Property [i ].Name == prop .Name {
266
+ idx = i
267
+ }
268
+ customProps .Property = append (customProps .Property , * p )
269
+ }
270
+ if idx != - 1 && prop .Value == nil {
271
+ customProps .Property = slices .Delete (customProps .Property , idx , idx + 1 )
272
+ }
273
+ if prop .Value != nil {
274
+ property := xlsxProperty {Name : prop .Name , FmtID : EXtURICustomPropertyFmtID }
275
+ if err := property .setCustomProps (prop .Value ); err != nil {
276
+ return err
277
+ }
278
+ if idx != - 1 {
279
+ property .PID = customProps .Property [idx ].PID
280
+ customProps .Property [idx ] = property
281
+ } else {
282
+ property .PID = pID + 1
283
+ customProps .Property = append (customProps .Property , property )
284
+ }
285
+ }
286
+ _ = f .addRels (defaultXMLPathRels , SourceRelationshipCustomProperties , defaultXMLPathDocPropsCustom , "" )
287
+ if err := f .addContentTypePart (0 , "customProperties" ); err != nil {
288
+ return err
289
+ }
290
+ output , err := xml .Marshal (customProps )
291
+ f .saveFileList (defaultXMLPathDocPropsCustom , output )
292
+ return err
293
+ }
294
+
295
+ // setCustomProps sets the custom property value based on its type.
296
+ func (prop * xlsxProperty ) setCustomProps (value interface {}) error {
297
+ switch v := value .(type ) {
298
+ case int32 :
299
+ prop .I4 = & v
300
+ case float64 :
301
+ prop .R8 = float64Ptr (v )
302
+ case bool :
303
+ prop .Bool = boolPtr (v )
304
+ case string :
305
+ prop .Lpwstr = stringPtr (value .(string ))
306
+ case time.Time :
307
+ prop .FileTime = stringPtr (value .(time.Time ).Format (time .RFC3339 ))
308
+ default :
309
+ return ErrParameterInvalid
310
+ }
311
+ return nil
312
+ }
313
+
314
+ // GetCustomProps provides a function to get custom file properties.
315
+ func (f * File ) GetCustomProps () ([]CustomProperty , error ) {
316
+ var customProps []CustomProperty
317
+ props := new (decodeCustomProperties )
318
+ if err := f .xmlNewDecoder (bytes .NewReader (namespaceStrictToTransitional (f .readXML (defaultXMLPathDocPropsCustom )))).
319
+ Decode (props ); err != nil && err != io .EOF {
320
+ return customProps , err
321
+ }
322
+ for _ , p := range props .Property {
323
+ prop := CustomProperty {Name : p .Name }
324
+ var err error
325
+ if prop .Value , err = p .getCustomProps (); err != nil {
326
+ return customProps , err
327
+ }
328
+ customProps = append (customProps , prop )
329
+ }
330
+ return customProps , nil
331
+ }
332
+
333
+ // getCustomProps gets the custom property value based on its type.
334
+ func (p * decodeProperty ) getCustomProps () (interface {}, error ) {
335
+ s := reflect .ValueOf (p ).Elem ()
336
+ for i := range s .NumField () {
337
+ if 11 <= i && i <= 20 && ! s .Field (i ).IsNil () {
338
+ return int32 (s .Field (i ).Elem ().Int ()), nil // Field vt:i1 to vt:uint
339
+ }
340
+ if 21 <= i && i <= 22 && ! s .Field (i ).IsNil () {
341
+ return s .Field (i ).Elem ().Float (), nil // Field vt:r4 to vt:r8
342
+ }
343
+ if p .Bool != nil {
344
+ return * p .Bool , nil
345
+ }
346
+ if p .Lpwstr != nil {
347
+ return * p .Lpwstr , nil
348
+ }
349
+ if p .FileTime != nil {
350
+ return time .Parse (time .RFC3339 , * p .FileTime )
351
+ }
352
+ }
353
+ return nil , nil
354
+ }
0 commit comments