Skip to content

Commit

Permalink
Introduce 2 new functions SetCalcProps and GetCalcProps (#2098)
Browse files Browse the repository at this point in the history
- Add new CalcPropsOptions data type
- Support assign exported data structure fields value to internal data structure fields dynamically by specified fields name list
- Simplify code for function SetAppProps, SetDocProps, SetWorkbookProps, GetWorkbookProps and getPivotTable
- Update unit tests
  • Loading branch information
romanshevelev authored and xuri committed Mar 4, 2025
1 parent c6d161f commit aef20e2
Show file tree
Hide file tree
Showing 6 changed files with 182 additions and 81 deletions.
57 changes: 11 additions & 46 deletions docProps.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,34 +65,15 @@ import (
// AppVersion: "16.0000",
// })
func (f *File) SetAppProps(appProperties *AppProperties) error {
var (
app *xlsxProperties
err error
field string
fields []string
immutable, mutable reflect.Value
output []byte
)
app = new(xlsxProperties)
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathDocPropsApp)))).
app := new(xlsxProperties)
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathDocPropsApp)))).
Decode(app); err != nil && err != io.EOF {
return err
}
fields = []string{"Application", "ScaleCrop", "DocSecurity", "Company", "LinksUpToDate", "HyperlinksChanged", "AppVersion"}
immutable, mutable = reflect.ValueOf(*appProperties), reflect.ValueOf(app).Elem()
for _, field = range fields {
immutableField := immutable.FieldByName(field)
switch immutableField.Kind() {
case reflect.Bool:
mutable.FieldByName(field).SetBool(immutableField.Bool())
case reflect.Int:
mutable.FieldByName(field).SetInt(immutableField.Int())
default:
mutable.FieldByName(field).SetString(immutableField.String())
}
}
setNoPtrFieldsVal([]string{"Application", "ScaleCrop", "DocSecurity", "Company", "LinksUpToDate", "HyperlinksChanged", "AppVersion"},
reflect.ValueOf(*appProperties), reflect.ValueOf(app).Elem())
app.Vt = NameSpaceDocumentPropertiesVariantTypes.Value
output, err = xml.Marshal(app)
output, err := xml.Marshal(app)
f.saveFileList(defaultXMLPathDocPropsApp, output)
return err
}
Expand Down Expand Up @@ -180,22 +161,12 @@ func (f *File) GetAppProps() (ret *AppProperties, err error) {
// Version: "1.0.0",
// })
func (f *File) SetDocProps(docProperties *DocProperties) error {
var (
core *decodeCoreProperties
err error
field, val string
fields []string
immutable, mutable reflect.Value
newProps *xlsxCoreProperties
output []byte
)

core = new(decodeCoreProperties)
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathDocPropsCore)))).
core := new(decodeCoreProperties)
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathDocPropsCore)))).
Decode(core); err != nil && err != io.EOF {
return err
}
newProps = &xlsxCoreProperties{
newProps := &xlsxCoreProperties{
Dc: NameSpaceDublinCore,
Dcterms: NameSpaceDublinCoreTerms,
Dcmitype: NameSpaceDublinCoreMetadataInitiative,
Expand All @@ -219,23 +190,17 @@ func (f *File) SetDocProps(docProperties *DocProperties) error {
if core.Modified != nil {
newProps.Modified = &xlsxDcTerms{Type: core.Modified.Type, Text: core.Modified.Text}
}
fields = []string{
setNoPtrFieldsVal([]string{
"Category", "ContentStatus", "Creator", "Description", "Identifier", "Keywords",
"LastModifiedBy", "Revision", "Subject", "Title", "Language", "Version",
}
immutable, mutable = reflect.ValueOf(*docProperties), reflect.ValueOf(newProps).Elem()
for _, field = range fields {
if val = immutable.FieldByName(field).String(); val != "" {
mutable.FieldByName(field).SetString(val)
}
}
}, reflect.ValueOf(*docProperties), reflect.ValueOf(newProps).Elem())
if docProperties.Created != "" {
newProps.Created = &xlsxDcTerms{Type: "dcterms:W3CDTF", Text: docProperties.Created}
}
if docProperties.Modified != "" {
newProps.Modified = &xlsxDcTerms{Type: "dcterms:W3CDTF", Text: docProperties.Modified}
}
output, err = xml.Marshal(newProps)
output, err := xml.Marshal(newProps)
f.saveFileList(defaultXMLPathDocPropsCore, output)

return err
Expand Down
49 changes: 49 additions & 0 deletions lib.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"math"
"math/big"
"os"
"reflect"
"regexp"
"strconv"
"strings"
Expand Down Expand Up @@ -878,6 +879,54 @@ func continuedFraction(n float64, i int64, limit int64, prec float64) *big.Rat {
return res
}

// assignFieldValue assigns the value from an immutable reflect.Value to a
// mutable reflect.Value based on the type of the immutable value.
func assignFieldValue(field string, immutable, mutable reflect.Value) {
switch immutable.Kind() {
case reflect.Bool:
mutable.FieldByName(field).SetBool(immutable.Bool())
case reflect.Int:
mutable.FieldByName(field).SetInt(immutable.Int())
default:
mutable.FieldByName(field).SetString(immutable.String())
}
}

// setNoPtrFieldsVal assigns values from the pointer or no-pointer structs
// fields (immutable) value to no-pointer struct field.
func setNoPtrFieldsVal(fields []string, immutable, mutable reflect.Value) {
for _, field := range fields {
immutableField := immutable.FieldByName(field)
if immutableField.Kind() == reflect.Ptr {
if immutableField.IsValid() && !immutableField.IsNil() {
assignFieldValue(field, immutableField.Elem(), mutable)
}
continue
}
assignFieldValue(field, immutableField, mutable)
}
}

// setPtrFieldsVal assigns values from the pointer or no-pointer structs
// fields (immutable) value to pointer struct field.
func setPtrFieldsVal(fields []string, immutable, mutable reflect.Value) {
for _, field := range fields {
immutableField := immutable.FieldByName(field)
if immutableField.Kind() == reflect.Ptr {
if immutableField.IsValid() && !immutableField.IsNil() {
mutable.FieldByName(field).Set(immutableField.Elem())
}
continue
}
if immutableField.IsZero() {
continue
}
ptr := reflect.New(immutableField.Type())
ptr.Elem().Set(immutableField)
mutable.FieldByName(field).Set(ptr)
}
}

// Stack defined an abstract data type that serves as a collection of elements.
type Stack struct {
list *list.List
Expand Down
25 changes: 6 additions & 19 deletions pivotTable.go
Original file line number Diff line number Diff line change
Expand Up @@ -883,14 +883,10 @@ func (f *File) getPivotTable(sheet, pivotTableXML, pivotCacheRels string) (Pivot
opts.DataRange = pc.CacheSource.WorksheetSource.Name
_ = f.getPivotTableDataRange(&opts)
}
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())
}
}
setPtrFieldsVal([]string{
"RowGrandTotals", "ColGrandTotals", "ShowDrill",
"UseAutoFormatting", "PageOverThenDown", "MergeItem", "CompactData", "ShowError",
}, reflect.ValueOf(*pt), reflect.ValueOf(&opts).Elem())
if si := pt.PivotTableStyleInfo; si != nil {
opts.ShowRowHeaders = si.ShowRowHeaders
opts.ShowColHeaders = si.ShowColHeaders
Expand Down Expand Up @@ -982,17 +978,8 @@ func extractPivotTableField(data string, fld *xlsxPivotField) PivotTableField {
ShowAll: fld.ShowAll,
InsertBlankRow: fld.InsertBlankRow,
}
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())
}
}
setPtrFieldsVal([]string{"Compact", "Name", "Outline", "DefaultSubtotal"},
reflect.ValueOf(*fld), reflect.ValueOf(&pivotTableField).Elem())
return pivotTableField
}

Expand Down
76 changes: 61 additions & 15 deletions workbook.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,46 +16,92 @@ import (
"encoding/xml"
"io"
"path/filepath"
"reflect"
"strconv"
"strings"
)

// SetWorkbookProps provides a function to sets workbook properties.
func (f *File) SetWorkbookProps(opts *WorkbookPropsOptions) error {
if opts == nil {
return nil
}
wb, err := f.workbookReader()
if err != nil {
return err
}
if wb.WorkbookPr == nil {
wb.WorkbookPr = new(xlsxWorkbookPr)
}
setNoPtrFieldsVal([]string{"Date1904", "FilterPrivacy", "CodeName"},
reflect.ValueOf(*opts), reflect.ValueOf(wb.WorkbookPr).Elem())
return err
}

// GetWorkbookProps provides a function to gets workbook properties.
func (f *File) GetWorkbookProps() (WorkbookPropsOptions, error) {
var opts WorkbookPropsOptions
wb, err := f.workbookReader()
if err != nil {
return opts, err
}
if wb.WorkbookPr == nil {
return opts, err
}
setPtrFieldsVal([]string{"Date1904", "FilterPrivacy", "CodeName"},
reflect.ValueOf(*wb.WorkbookPr), reflect.ValueOf(&opts).Elem())
return opts, err
}

// SetCalcProps provides a function to sets calculation properties.
func (f *File) SetCalcProps(opts *CalcPropsOptions) error {
if opts == nil {
return nil
}
if opts.Date1904 != nil {
wb.WorkbookPr.Date1904 = *opts.Date1904
wb, err := f.workbookReader()
if err != nil {
return err
}
if wb.CalcPr == nil {
wb.CalcPr = new(xlsxCalcPr)
}
if opts.FilterPrivacy != nil {
wb.WorkbookPr.FilterPrivacy = *opts.FilterPrivacy
setNoPtrFieldsVal([]string{
"CalcCompleted", "CalcOnSave", "ForceFullCalc", "FullCalcOnLoad", "FullPrecision", "Iterate",
"IterateDelta",
"CalcMode", "RefMode",
}, reflect.ValueOf(*opts), reflect.ValueOf(wb.CalcPr).Elem())
if opts.CalcID != nil {
wb.CalcPr.CalcID = int(*opts.CalcID)
}
if opts.CodeName != nil {
wb.WorkbookPr.CodeName = *opts.CodeName
if opts.ConcurrentManualCount != nil {
wb.CalcPr.ConcurrentManualCount = int(*opts.ConcurrentManualCount)
}
return nil
if opts.IterateCount != nil {
wb.CalcPr.IterateCount = int(*opts.IterateCount)
}
wb.CalcPr.ConcurrentCalc = opts.ConcurrentCalc
return err
}

// GetWorkbookProps provides a function to gets workbook properties.
func (f *File) GetWorkbookProps() (WorkbookPropsOptions, error) {
var opts WorkbookPropsOptions
// GetCalcProps provides a function to gets calculation properties.
func (f *File) GetCalcProps() (CalcPropsOptions, error) {
var opts CalcPropsOptions
wb, err := f.workbookReader()
if err != nil {
return opts, err
}
if wb.WorkbookPr != nil {
opts.Date1904 = boolPtr(wb.WorkbookPr.Date1904)
opts.FilterPrivacy = boolPtr(wb.WorkbookPr.FilterPrivacy)
opts.CodeName = stringPtr(wb.WorkbookPr.CodeName)
if wb.CalcPr == nil {
return opts, err
}
setPtrFieldsVal([]string{
"CalcCompleted", "CalcOnSave", "ForceFullCalc", "FullCalcOnLoad", "FullPrecision", "Iterate",
"IterateDelta",
"CalcMode", "RefMode",
}, reflect.ValueOf(*wb.CalcPr), reflect.ValueOf(&opts).Elem())
opts.CalcID = uintPtr(uint(wb.CalcPr.CalcID))
opts.ConcurrentManualCount = uintPtr(uint(wb.CalcPr.ConcurrentManualCount))
opts.IterateCount = uintPtr(uint(wb.CalcPr.IterateCount))
opts.ConcurrentCalc = wb.CalcPr.ConcurrentCalc
return opts, err
}

Expand Down Expand Up @@ -99,7 +145,7 @@ func (f *File) ProtectWorkbook(opts *WorkbookProtectionOptions) error {
wb.WorkbookProtection.WorkbookHashValue = hashValue
wb.WorkbookProtection.WorkbookSpinCount = int(workbookProtectionSpinCount)
}
return nil
return err
}

// UnprotectWorkbook provides a function to remove protection for workbook,
Expand Down
36 changes: 36 additions & 0 deletions workbook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ func TestWorkbookProps(t *testing.T) {
opts, err := f.GetWorkbookProps()
assert.NoError(t, err)
assert.Equal(t, expected, opts)
wb.WorkbookPr = nil
opts, err = f.GetWorkbookProps()
assert.NoError(t, err)
assert.Equal(t, WorkbookPropsOptions{}, opts)
// Test set workbook properties with unsupported charset workbook
f.WorkBook = nil
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
Expand All @@ -32,6 +36,38 @@ func TestWorkbookProps(t *testing.T) {
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
}

func TestCalcProps(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetCalcProps(nil))
wb, err := f.workbookReader()
assert.NoError(t, err)
wb.CalcPr = nil
expected := CalcPropsOptions{
FullCalcOnLoad: boolPtr(true),
CalcID: uintPtr(122211),
ConcurrentManualCount: uintPtr(5),
IterateCount: uintPtr(10),
ConcurrentCalc: boolPtr(true),
}
assert.NoError(t, f.SetCalcProps(&expected))
opts, err := f.GetCalcProps()
assert.NoError(t, err)
assert.Equal(t, expected, opts)
wb.CalcPr = nil
opts, err = f.GetCalcProps()
assert.NoError(t, err)
assert.Equal(t, CalcPropsOptions{}, opts)
// Test set workbook properties with unsupported charset workbook
f.WorkBook = nil
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
assert.EqualError(t, f.SetCalcProps(&expected), "XML syntax error on line 1: invalid UTF-8")
// Test get workbook properties with unsupported charset workbook
f.WorkBook = nil
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
_, err = f.GetCalcProps()
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
}

func TestDeleteWorkbookRels(t *testing.T) {
f := NewFile()
// Test delete pivot table without worksheet relationships
Expand Down
20 changes: 19 additions & 1 deletion xmlWorkbook.go
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ type xlsxDefinedName struct {
// displaying the results as values in the cells that contain the formulas.
type xlsxCalcPr struct {
CalcCompleted bool `xml:"calcCompleted,attr,omitempty"`
CalcID string `xml:"calcId,attr,omitempty"`
CalcID int `xml:"calcId,attr,omitempty"`
CalcMode string `xml:"calcMode,attr,omitempty"`
CalcOnSave bool `xml:"calcOnSave,attr,omitempty"`
ConcurrentCalc *bool `xml:"concurrentCalc,attr"`
Expand Down Expand Up @@ -384,6 +384,24 @@ type DefinedName struct {
Scope string
}

// CalcPropsOptions defines the collection of properties the application uses to
// record calculation status and details.
type CalcPropsOptions struct {
CalcID *uint `xml:"calcId,attr"`
CalcMode *string `xml:"calcMode,attr"`
FullCalcOnLoad *bool `xml:"fullCalcOnLoad,attr"`
RefMode *string `xml:"refMode,attr"`
Iterate *bool `xml:"iterate,attr"`
IterateCount *uint `xml:"iterateCount,attr"`
IterateDelta *float64 `xml:"iterateDelta,attr"`
FullPrecision *bool `xml:"fullPrecision,attr"`
CalcCompleted *bool `xml:"calcCompleted,attr"`
CalcOnSave *bool `xml:"calcOnSave,attr"`
ConcurrentCalc *bool `xml:"concurrentCalc,attr"`
ConcurrentManualCount *uint `xml:"concurrentManualCount,attr"`
ForceFullCalc *bool `xml:"forceFullCalc,attr"`
}

// WorkbookPropsOptions directly maps the settings of workbook proprieties.
type WorkbookPropsOptions struct {
Date1904 *bool
Expand Down

0 comments on commit aef20e2

Please sign in to comment.