Introduce 2 new functions SetCalcProps and GetCalcProps ()

- 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
This commit is contained in:
Roman Shevelev 2025-03-04 18:20:24 +05:00 committed by xuri
parent c6d161fc76
commit aef20e226c
No known key found for this signature in database
GPG Key ID: BA5E5BB1C948EDF7
6 changed files with 185 additions and 84 deletions

View File

@ -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
}
@ -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,
@ -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

49
lib.go
View File

@ -21,6 +21,7 @@ import (
"math"
"math/big"
"os"
"reflect"
"regexp"
"strconv"
"strings"
@ -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

View File

@ -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
@ -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
}

View File

@ -16,12 +16,16 @@ 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
@ -29,19 +33,9 @@ func (f *File) SetWorkbookProps(opts *WorkbookPropsOptions) error {
if wb.WorkbookPr == nil {
wb.WorkbookPr = new(xlsxWorkbookPr)
}
if opts == nil {
return nil
}
if opts.Date1904 != nil {
wb.WorkbookPr.Date1904 = *opts.Date1904
}
if opts.FilterPrivacy != nil {
wb.WorkbookPr.FilterPrivacy = *opts.FilterPrivacy
}
if opts.CodeName != nil {
wb.WorkbookPr.CodeName = *opts.CodeName
}
return nil
setNoPtrFieldsVal([]string{"Date1904", "FilterPrivacy", "CodeName"},
reflect.ValueOf(*opts), reflect.ValueOf(wb.WorkbookPr).Elem())
return err
}
// GetWorkbookProps provides a function to gets workbook properties.
@ -51,11 +45,63 @@ func (f *File) GetWorkbookProps() (WorkbookPropsOptions, error) {
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.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
}
wb, err := f.workbookReader()
if err != nil {
return err
}
if wb.CalcPr == nil {
wb.CalcPr = new(xlsxCalcPr)
}
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.ConcurrentManualCount != nil {
wb.CalcPr.ConcurrentManualCount = int(*opts.ConcurrentManualCount)
}
if opts.IterateCount != nil {
wb.CalcPr.IterateCount = int(*opts.IterateCount)
}
wb.CalcPr.ConcurrentCalc = opts.ConcurrentCalc
return err
}
// 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.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
}
@ -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,

View File

@ -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)
@ -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

View File

@ -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"`
@ -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