Skip to content

Commit

Permalink
This closes qax-os#1835, support get data validations which storage i…
Browse files Browse the repository at this point in the history
…n the extension lists
  • Loading branch information
xuri committed Mar 5, 2024
1 parent 15003a9 commit 5af7e2e
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 160 deletions.
2 changes: 1 addition & 1 deletion cell_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ func TestConcurrency(t *testing.T) {
// Concurrency add data validation
dv := NewDataValidation(true)
dv.Sqref = fmt.Sprintf("A%d:B%d", val, val)
dv.SetRange(10, 20, DataValidationTypeWhole, DataValidationOperatorGreaterThan)
assert.NoError(t, dv.SetRange(10, 20, DataValidationTypeWhole, DataValidationOperatorGreaterThan))
dv.SetInput(fmt.Sprintf("title:%d", val), strconv.Itoa(val))
assert.NoError(t, f.AddDataValidation("Sheet1", dv))
// Concurrency delete data validation with reference sequence
Expand Down
149 changes: 55 additions & 94 deletions datavalidation.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ package excelize

import (
"fmt"
"io"
"math"
"strings"
"unicode/utf16"
Expand Down Expand Up @@ -293,110 +294,70 @@ func (f *File) GetDataValidations(sheet string) ([]*DataValidation, error) {
if err != nil {
return nil, err
}
if ws.DataValidations == nil || len(ws.DataValidations.DataValidation) == 0 {
return nil, err
var (
dataValidations []*DataValidation
decodeExtLst = new(decodeExtLst)
decodeDataValidations *xlsxDataValidations
ext *xlsxExt
)
if ws.DataValidations != nil {
dataValidations = append(dataValidations, getDataValidations(ws.DataValidations)...)
}
var dvs []*DataValidation
for _, dv := range ws.DataValidations.DataValidation {
if dv != nil {
dataValidation := &DataValidation{
AllowBlank: dv.AllowBlank,
Error: dv.Error,
ErrorStyle: dv.ErrorStyle,
ErrorTitle: dv.ErrorTitle,
Operator: dv.Operator,
Prompt: dv.Prompt,
PromptTitle: dv.PromptTitle,
ShowDropDown: dv.ShowDropDown,
ShowErrorMessage: dv.ShowErrorMessage,
ShowInputMessage: dv.ShowInputMessage,
Sqref: dv.Sqref,
Type: dv.Type,
}
if dv.Formula1 != nil {
dataValidation.Formula1 = unescapeDataValidationFormula(dv.Formula1.Content)
}
if dv.Formula2 != nil {
dataValidation.Formula2 = unescapeDataValidationFormula(dv.Formula2.Content)
if ws.ExtLst != nil {
if err = f.xmlNewDecoder(strings.NewReader("<extLst>" + ws.ExtLst.Ext + "</extLst>")).
Decode(decodeExtLst); err != nil && err != io.EOF {
return dataValidations, err
}
for _, ext = range decodeExtLst.Ext {
if ext.URI == ExtURIDataValidations {
decodeDataValidations = new(xlsxDataValidations)
_ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(decodeDataValidations)
dataValidations = append(dataValidations, getDataValidations(decodeDataValidations)...)
}
dvs = append(dvs, dataValidation)
}
}
return dvs, err
return dataValidations, err
}

// GetAllLinkedDataValidations The GetDataValidations method sometimes fails to retrieve all data validations completely, including those linked to other pages.
// This method serves as a temporary fix for this issue, allowing the retrieval of associated data validation content from other sheets through the Ext structure.
// Please note that this is an interim solution and the Ext structure may contain more than just the data validation components.
func (f *File) GetAllLinkedDataValidations(sheet string) ([]*DataValidation, error) {
ws, err := f.workSheetReader(sheet)
if err != nil {
return nil, err
}
if ws.DataValidations == nil || len(ws.DataValidations.DataValidation) == 0 {
return nil, err
// getDataValidations returns data validations list by given worksheet data
// validations.
func getDataValidations(dvs *xlsxDataValidations) []*DataValidation {
if dvs == nil {
return nil
}
var dvs []*DataValidation
if ws.ExtLst != nil {
extStruct, err := ParseXMLToStruct(strings.NewReader(ws.ExtLst.Ext))
if err != nil {
fmt.Println("Error parsing XML to struct:", err)
} else {
if extStruct != nil && extStruct.LinkDataValidations != nil {
for _, v := range extStruct.LinkDataValidations.DataValidation {
linkDataValidation := &xlsxDataValidation{
AllowBlank: v.AllowBlank,
Error: v.Error,
ErrorStyle: v.ErrorStyle,
ErrorTitle: v.ErrorTitle,
Operator: v.Operator,
Prompt: v.Prompt,
PromptTitle: v.PromptTitle,
ShowDropDown: v.ShowDropDown,
ShowErrorMessage: v.ShowErrorMessage,
ShowInputMessage: v.ShowInputMessage,
Sqref: v.Sqref,
Type: v.Type,
}
if v.Formula1 != nil {
linkDataValidation.Formula1 = &xlsxInnerXML{Content: v.Formula1.F}
}
if v.Formula2 != nil {
linkDataValidation.Formula2 = &xlsxInnerXML{Content: v.Formula2.F}
}
ws.DataValidations.DataValidation = append(ws.DataValidations.DataValidation, linkDataValidation)
}

}
var dataValidations []*DataValidation
for _, dv := range dvs.DataValidation {
if dv == nil {
continue
}
//ws.DataValidations.DataValidation = append(ws.DataValidations.DataValidation, ws.ExtLst.DataValidations.DataValidation...)
}
for _, dv := range ws.DataValidations.DataValidation {
if dv != nil {
dataValidation := &DataValidation{
AllowBlank: dv.AllowBlank,
Error: dv.Error,
ErrorStyle: dv.ErrorStyle,
ErrorTitle: dv.ErrorTitle,
Operator: dv.Operator,
Prompt: dv.Prompt,
PromptTitle: dv.PromptTitle,
ShowDropDown: dv.ShowDropDown,
ShowErrorMessage: dv.ShowErrorMessage,
ShowInputMessage: dv.ShowInputMessage,
Sqref: dv.Sqref,
Type: dv.Type,
}
if dv.Formula1 != nil {
dataValidation.Formula1 = unescapeDataValidationFormula(dv.Formula1.Content)
}
if dv.Formula2 != nil {
dataValidation.Formula2 = unescapeDataValidationFormula(dv.Formula2.Content)
}
dvs = append(dvs, dataValidation)
dataValidation := &DataValidation{
AllowBlank: dv.AllowBlank,
Error: dv.Error,
ErrorStyle: dv.ErrorStyle,
ErrorTitle: dv.ErrorTitle,
Operator: dv.Operator,
Prompt: dv.Prompt,
PromptTitle: dv.PromptTitle,
ShowDropDown: dv.ShowDropDown,
ShowErrorMessage: dv.ShowErrorMessage,
ShowInputMessage: dv.ShowInputMessage,
Sqref: dv.Sqref,
Type: dv.Type,
}
if dv.Formula1 != nil {
dataValidation.Formula1 = unescapeDataValidationFormula(dv.Formula1.Content)
}
if dv.Formula2 != nil {
dataValidation.Formula2 = unescapeDataValidationFormula(dv.Formula2.Content)
}
if dv.XMSqref != "" {
dataValidation.Sqref = dv.XMSqref
dataValidation.Formula1 = strings.TrimSuffix(strings.TrimPrefix(dataValidation.Formula1, "<xm:f>"), "</xm:f>")
dataValidation.Formula2 = strings.TrimSuffix(strings.TrimPrefix(dataValidation.Formula2, "<xm:f>"), "</xm:f>")
}
dataValidations = append(dataValidations, dataValidation)
}
return dvs, err
return dataValidations
}

// DeleteDataValidation delete data validation by given worksheet name and
Expand Down
42 changes: 26 additions & 16 deletions datavalidation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
package excelize

import (
"log"
"fmt"
"math"
"path/filepath"
"strings"
Expand All @@ -21,21 +21,6 @@ import (
"github.com/stretchr/testify/assert"
)

func TestGetA(t *testing.T) {
file, err := OpenFile("file2.xlsx")
if err != nil {
panic(err)
}
validations, err := file.GetAllLinkedDataValidations("5")
if err != nil {
panic(err)
}

for i := range validations {
log.Println(validations[i].Type)
}
}

func TestDataValidation(t *testing.T) {
resultFile := filepath.Join("test", "TestDataValidation.xlsx")

Expand Down Expand Up @@ -120,6 +105,31 @@ func TestDataValidation(t *testing.T) {
dataValidations, err = f.GetDataValidations("Sheet1")
assert.NoError(t, err)
assert.Equal(t, []*DataValidation(nil), dataValidations)

// Test get data validations which storage in the extension lists
f = NewFile()
ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).ExtLst = &xlsxExtLst{Ext: fmt.Sprintf(`<ext uri="%s" xmlns:x14="%s"><x14:dataValidations><x14:dataValidation type="list" allowBlank="1"><x14:formula1><xm:f>Sheet1!$B$1:$B$5</xm:f></x14:formula1><xm:sqref>A7:B8</xm:sqref></x14:dataValidation></x14:dataValidations></ext>`, ExtURIDataValidations, NameSpaceSpreadSheetX14.Value)}
dataValidations, err = f.GetDataValidations("Sheet1")
assert.NoError(t, err)
assert.Equal(t, []*DataValidation{
{
AllowBlank: true,
Type: "list",
Formula1: "Sheet1!$B$1:$B$5",
Sqref: "A7:B8",
},
}, dataValidations)

// Test get data validations with invalid extension list characters
ws.(*xlsxWorksheet).ExtLst = &xlsxExtLst{Ext: fmt.Sprintf(`<ext uri="%s" xmlns:x14="%s"><x14:dataValidations></x14:dataValidation></x14:dataValidations></ext>`, ExtURIDataValidations, NameSpaceSpreadSheetX14.Value)}
_, err = f.GetDataValidations("Sheet1")
assert.EqualError(t, err, "XML syntax error on line 1: element <dataValidations> closed by </dataValidation>")

// Test get validations without validations
assert.Nil(t, getDataValidations(nil))
assert.Nil(t, getDataValidations(&xlsxDataValidations{DataValidation: []*xlsxDataValidation{nil}}))
}

func TestDataValidationError(t *testing.T) {
Expand Down
11 changes: 0 additions & 11 deletions excelize.go
Original file line number Diff line number Diff line change
Expand Up @@ -588,14 +588,3 @@ func (f *File) setContentTypePartProjectExtensions(contentType string) error {
}
return err
}

// ParseXMLToStruct takes an XML reader and parses it into the Ext struct.
func ParseXMLToStruct(r io.Reader) (*Ext, error) {
var ext Ext
decoder := xml.NewDecoder(r)
err := decoder.Decode(&ext)
if err != nil {
return nil, err
}
return &ext, nil
}
2 changes: 1 addition & 1 deletion templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ const (
ExtURIConditionalFormattingRuleID = "{B025F937-C7B1-47D3-B67F-A62EFF666E3E}"
ExtURIConditionalFormattings = "{78C0D931-6437-407d-A8EE-F0AAD7539E65}"
ExtURIDataModel = "{FCE2AD5D-F65C-4FA6-A056-5C36A1767C68}"
ExtURIDataValidations = "{CCE6A557-97BC-4B89-ADB6-D9C93CAAB3DF}"
ExtURIDataValidations = "{CCE6A557-97BC-4b89-ADB6-D9C93CAAB3DF}"
ExtURIDrawingBlip = "{28A0092B-C50C-407E-A947-70E740481C1C}"
ExtURIExternalLinkPr = "{FCE6A71B-6B00-49CD-AB44-F6B1AE7CDE65}"
ExtURIIgnoredErrors = "{01252117-D84E-4E92-8308-4BE1C098FCBB}"
Expand Down
37 changes: 0 additions & 37 deletions xmlWorkbook.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,43 +231,6 @@ type xlsxExt struct {
xmlns []xml.Attr
}

// Ext The Ext structure is used to parse XML and obtain relevant content from the *xlsxExtLst structure.
type Ext struct {
XMLName xml.Name `xml:"ext"`
URI string `xml:"uri,attr"`
LinkDataValidations *LinkDataValidations `xml:"http://schemas.microsoft.com/office/spreadsheetml/2009/9/main dataValidations"`
}

// LinkDataValidations used to Ext
type LinkDataValidations struct {
Count int `xml:"count,attr"`
DataValidation []*LinkDataValidation `xml:"dataValidation"`
}

// LinkDataValidation used to LinkDataValidations
type LinkDataValidation struct {
AllowBlank bool `xml:"allowBlank,attr"`
Error *string `xml:"error,attr"`
ErrorStyle *string `xml:"errorStyle,attr"`
ErrorTitle *string `xml:"errorTitle,attr"`
Operator string `xml:"operator,attr,omitempty"`
Prompt *string `xml:"prompt,attr"`
PromptTitle *string `xml:"promptTitle,attr"`
ShowDropDown bool `xml:"showDropDown,attr,omitempty"`
ShowErrorMessage bool `xml:"showErrorMessage,attr,omitempty"`
ShowInputMessage bool `xml:"showInputMessage,attr,omitempty"`
Sqref string `xml:"sqref"`
UID string `xml:"uid,attr"`
Type string `xml:"type,attr"`
Formula1 *Formula `xml:"formula1"`
Formula2 *Formula `xml:"formula2"`
}

// Formula used to LinkDataValidation
type Formula struct {
F string `xml:"f"`
}

// xlsxAlternateContent is a container for a sequence of multiple
// representations of a given piece of content. The program reading the file
// should only process one of these, and the one chosen should be based on
Expand Down
1 change: 1 addition & 0 deletions xmlWorksheet.go
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,7 @@ type xlsxDataValidation struct {
ShowErrorMessage bool `xml:"showErrorMessage,attr,omitempty"`
ShowInputMessage bool `xml:"showInputMessage,attr,omitempty"`
Sqref string `xml:"sqref,attr"`
XMSqref string `xml:"sqref,omitempty"`
Type string `xml:"type,attr,omitempty"`
Formula1 *xlsxInnerXML `xml:"formula1"`
Formula2 *xlsxInnerXML `xml:"formula2"`
Expand Down

0 comments on commit 5af7e2e

Please sign in to comment.