Skip to content

Commit bb1105b

Browse files
authored
This closes qax-os#2133, support delete data validations in the ext list (qax-os#2137)
- Support delete data validation by given with multiple cell ranges with reference sequence slice or blank separated reference sequence string - Update unit tests
1 parent 72b731a commit bb1105b

File tree

3 files changed

+158
-5
lines changed

3 files changed

+158
-5
lines changed

datavalidation.go

Lines changed: 97 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@
1212
package excelize
1313

1414
import (
15+
"encoding/xml"
1516
"fmt"
1617
"io"
1718
"math"
19+
"slices"
1820
"strings"
1921
"unicode/utf16"
2022
)
@@ -361,27 +363,59 @@ func getDataValidations(dvs *xlsxDataValidations) []*DataValidation {
361363
}
362364

363365
// DeleteDataValidation delete data validation by given worksheet name and
364-
// reference sequence. This function is concurrency safe.
365-
// All data validations in the worksheet will be deleted
366-
// if not specify reference sequence parameter.
366+
// reference sequence. This function is concurrency safe. All data validations
367+
// in the worksheet will be deleted if not specify reference sequence parameter.
368+
//
369+
// Example 1, delete data validation on Sheet1!A1:B2:
370+
//
371+
// err := f.DeleteDataValidation("Sheet1", "A1:B2")
372+
//
373+
// Example 2, delete data validations on Sheet1 with multiple cell ranges
374+
// A1:B2 and C1:C3 with reference sequence slice:
375+
//
376+
// err := f.DeleteDataValidation("Sheet1", []string{"A1:B2", "C1:C3"}...)
377+
//
378+
// Example 3, delete data validations on Sheet1 with multiple cell ranges
379+
// A1:B2 and C1:C3 with blank separated reference sequence string, the result
380+
// same as example 2:
381+
//
382+
// err := f.DeleteDataValidation("Sheet1", "A1:B2 C1:C3")
383+
//
384+
// Example 4, delete all data validations on Sheet1:
385+
//
386+
// err := f.DeleteDataValidation("Sheet1")
367387
func (f *File) DeleteDataValidation(sheet string, sqref ...string) error {
368388
ws, err := f.workSheetReader(sheet)
369389
if err != nil {
370390
return err
371391
}
372392
ws.mu.Lock()
373393
defer ws.mu.Unlock()
374-
if ws.DataValidations == nil {
394+
if ws.DataValidations == nil && ws.ExtLst == nil {
375395
return nil
376396
}
377397
if sqref == nil {
378398
ws.DataValidations = nil
379399
return nil
380400
}
381-
delCells, err := flatSqref(sqref[0])
401+
delCells, err := flatSqref(strings.Join(sqref, " "))
382402
if err != nil {
383403
return err
384404
}
405+
if ws.DataValidations != nil {
406+
if err = f.deleteDataValidation(ws, delCells); err != nil {
407+
return err
408+
}
409+
}
410+
if ws.ExtLst != nil {
411+
return f.deleteX14DataValidation(ws, sqref)
412+
}
413+
return nil
414+
}
415+
416+
// deleteDataValidation deletes data validation by given worksheet and cell
417+
// reference list.
418+
func (f *File) deleteDataValidation(ws *xlsxWorksheet, delCells map[int][][]int) error {
385419
dv := ws.DataValidations
386420
for i := 0; i < len(dv.DataValidation); i++ {
387421
var applySqref []string
@@ -413,6 +447,64 @@ func (f *File) DeleteDataValidation(sheet string, sqref ...string) error {
413447
return nil
414448
}
415449

450+
// deleteX14DataValidation deletes data validation in the extLst element by
451+
// given worksheet and cell reference list.
452+
func (f *File) deleteX14DataValidation(ws *xlsxWorksheet, sqref []string) error {
453+
var (
454+
decodeExtLst = new(decodeExtLst)
455+
decodeDataValidations *xlsxDataValidations
456+
x14DataValidations *xlsxX14DataValidations
457+
)
458+
if err := f.xmlNewDecoder(strings.NewReader("<extLst>" + ws.ExtLst.Ext + "</extLst>")).
459+
Decode(decodeExtLst); err != nil && err != io.EOF {
460+
return err
461+
}
462+
for i, ext := range decodeExtLst.Ext {
463+
if ext.URI == ExtURIDataValidations {
464+
decodeDataValidations = new(xlsxDataValidations)
465+
x14DataValidations = new(xlsxX14DataValidations)
466+
_ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(decodeDataValidations)
467+
x14DataValidations.XMLNSXM = NameSpaceSpreadSheetExcel2006Main.Value
468+
x14DataValidations.DisablePrompts = decodeDataValidations.DisablePrompts
469+
x14DataValidations.XWindow = decodeDataValidations.XWindow
470+
x14DataValidations.YWindow = decodeDataValidations.YWindow
471+
for _, dv := range decodeDataValidations.DataValidation {
472+
if inStrSlice(sqref, dv.XMSqref, false) == -1 {
473+
x14DataValidations.DataValidation = append(x14DataValidations.DataValidation, &xlsxX14DataValidation{
474+
AllowBlank: dv.AllowBlank,
475+
Error: dv.Error,
476+
ErrorStyle: dv.ErrorStyle,
477+
ErrorTitle: dv.ErrorTitle,
478+
Operator: dv.Operator,
479+
Prompt: dv.Prompt,
480+
PromptTitle: dv.PromptTitle,
481+
ShowDropDown: dv.ShowDropDown,
482+
ShowErrorMessage: dv.ShowErrorMessage,
483+
ShowInputMessage: dv.ShowInputMessage,
484+
Sqref: dv.Sqref,
485+
XMSqref: dv.XMSqref,
486+
Type: dv.Type,
487+
Formula1: dv.Formula1,
488+
Formula2: dv.Formula2,
489+
})
490+
}
491+
}
492+
x14DataValidations.Count = len(x14DataValidations.DataValidation)
493+
x14DataValidationsBytes, _ := xml.Marshal(x14DataValidations)
494+
decodeExtLst.Ext[i] = &xlsxExt{
495+
xmlns: []xml.Attr{{Name: xml.Name{Local: "xmlns:" + NameSpaceSpreadSheetX14.Name.Local}, Value: NameSpaceSpreadSheetX14.Value}},
496+
URI: ExtURIDataValidations, Content: string(x14DataValidationsBytes),
497+
}
498+
if x14DataValidations.Count == 0 {
499+
decodeExtLst.Ext = slices.Delete(decodeExtLst.Ext, i, i+1)
500+
}
501+
}
502+
}
503+
extLstBytes, err := xml.Marshal(decodeExtLst)
504+
ws.ExtLst = &xlsxExtLst{Ext: strings.TrimSuffix(strings.TrimPrefix(string(extLstBytes), "<extLst>"), "</extLst>")}
505+
return err
506+
}
507+
416508
// squashSqref generates cell reference sequence by given cells coordinates list.
417509
func squashSqref(cells [][]int) []string {
418510
if len(cells) == 1 {

datavalidation_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"math"
1717
"path/filepath"
1818
"strings"
19+
"sync"
1920
"testing"
2021

2122
"github.com/stretchr/testify/assert"
@@ -242,4 +243,31 @@ func TestDeleteDataValidation(t *testing.T) {
242243
// Test delete all data validations in the worksheet
243244
assert.NoError(t, f.DeleteDataValidation("Sheet1"))
244245
assert.Nil(t, ws.(*xlsxWorksheet).DataValidations)
246+
247+
t.Run("delete_data_validation_from_extLst", func(t *testing.T) {
248+
f := NewFile()
249+
f.Sheet.Delete("xl/worksheets/sheet1.xml")
250+
f.Pkg.Store("xl/worksheets/sheet1.xml", fmt.Appendf(nil,
251+
`<worksheet xmlns="%s"><sheetData/><extLst><ext xmlns:x14="%s" uri="%s"><x14:dataValidations xmlns:xm="%s" count="2"><x14:dataValidation allowBlank="true" showErrorMessage="true" showInputMessage="true" sqref="" type="list"><xm:sqref>A1:A2</xm:sqref><x14:formula1><xm:f>Sheet1!$A$2:$A$4</xm:f></x14:formula1></x14:dataValidation><x14:dataValidation allowBlank="true" showErrorMessage="true" showInputMessage="true" sqref="" type="list"><xm:sqref>B1:B2</xm:sqref><x14:formula1><xm:f>Sheet1!$B$2:$B$3</xm:f></x14:formula1></x14:dataValidation></x14:dataValidations></ext></extLst></worksheet>`,
252+
NameSpaceSpreadSheet.Value, NameSpaceSpreadSheetExcel2006Main.Value,
253+
ExtURIDataValidations, NameSpaceSpreadSheetExcel2006Main.Value))
254+
f.checked = sync.Map{}
255+
assert.NoError(t, f.DeleteDataValidation("Sheet1", "A1:A2"))
256+
dvs, err := f.GetDataValidations("Sheet1")
257+
assert.NoError(t, err)
258+
assert.Len(t, dvs, 1)
259+
assert.Equal(t, "B1:B2", dvs[0].Sqref)
260+
261+
assert.NoError(t, f.DeleteDataValidation("Sheet1", "B1:B2"))
262+
dvs, err = f.GetDataValidations("Sheet1")
263+
assert.NoError(t, err)
264+
assert.Empty(t, dvs)
265+
})
266+
267+
t.Run("delete_data_validation_failed_from_extLst", func(t *testing.T) {
268+
f := NewFile()
269+
assert.EqualError(t, f.deleteX14DataValidation(&xlsxWorksheet{
270+
ExtLst: &xlsxExtLst{Ext: "<extLst><x14:dataValidations></x14:dataValidation></x14:dataValidations></ext></extLst>"},
271+
}, nil), "XML syntax error on line 1: element <dataValidations> closed by </dataValidation>")
272+
})
245273
}

xmlWorksheet.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,39 @@ type xlsxDataValidation struct {
448448
Formula2 *xlsxInnerXML `xml:"formula2"`
449449
}
450450

451+
// xlsxX14DataValidation directly maps the single item of data validation
452+
// defined on a extLst element of the worksheet.
453+
type xlsxX14DataValidation struct {
454+
XMLName xml.Name `xml:"x14:dataValidation"`
455+
AllowBlank bool `xml:"allowBlank,attr"`
456+
Error *string `xml:"error,attr"`
457+
ErrorStyle *string `xml:"errorStyle,attr"`
458+
ErrorTitle *string `xml:"errorTitle,attr"`
459+
Operator string `xml:"operator,attr,omitempty"`
460+
Prompt *string `xml:"prompt,attr"`
461+
PromptTitle *string `xml:"promptTitle,attr"`
462+
ShowDropDown bool `xml:"showDropDown,attr,omitempty"`
463+
ShowErrorMessage bool `xml:"showErrorMessage,attr,omitempty"`
464+
ShowInputMessage bool `xml:"showInputMessage,attr,omitempty"`
465+
Sqref string `xml:"sqref,attr"`
466+
Type string `xml:"type,attr,omitempty"`
467+
Formula1 *xlsxInnerXML `xml:"x14:formula1"`
468+
Formula2 *xlsxInnerXML `xml:"x14:formula2"`
469+
XMSqref string `xml:"xm:sqref,omitempty"`
470+
}
471+
472+
// xlsxX14DataValidations expresses all data validation information for cells in
473+
// a sheet extLst element which have data validation features applied.
474+
type xlsxX14DataValidations struct {
475+
XMLName xml.Name `xml:"x14:dataValidations"`
476+
XMLNSXM string `xml:"xmlns:xm,attr,omitempty"`
477+
Count int `xml:"count,attr,omitempty"`
478+
DisablePrompts bool `xml:"disablePrompts,attr,omitempty"`
479+
XWindow int `xml:"xWindow,attr,omitempty"`
480+
YWindow int `xml:"yWindow,attr,omitempty"`
481+
DataValidation []*xlsxX14DataValidation
482+
}
483+
451484
// xlsxC collection represents a cell in the worksheet. Information about the
452485
// cell's location (reference), value, data type, formatting, and formula is
453486
// expressed here.

0 commit comments

Comments
 (0)