diff --git a/adjust.go b/adjust.go index ef7b19a7adf2495866b84ca4b87321114e543337..1fe66634fee7642dc7d14f5ca6cc383938ef5925 100644 --- a/adjust.go +++ b/adjust.go @@ -11,10 +11,6 @@ package excelize -import ( - "strings" -) - type adjustDirection bool const ( @@ -194,62 +190,6 @@ func (f *File) adjustAutoFilterHelper(dir adjustDirection, coordinates []int, nu return coordinates } -// areaRefToCoordinates provides a function to convert area reference to a -// pair of coordinates. -func (f *File) areaRefToCoordinates(ref string) ([]int, error) { - rng := strings.Split(strings.Replace(ref, "$", "", -1), ":") - if len(rng) < 2 { - return nil, ErrParameterInvalid - } - - return areaRangeToCoordinates(rng[0], rng[1]) -} - -// areaRangeToCoordinates provides a function to convert cell range to a -// pair of coordinates. -func areaRangeToCoordinates(firstCell, lastCell string) ([]int, error) { - coordinates := make([]int, 4) - var err error - coordinates[0], coordinates[1], err = CellNameToCoordinates(firstCell) - if err != nil { - return coordinates, err - } - coordinates[2], coordinates[3], err = CellNameToCoordinates(lastCell) - return coordinates, err -} - -// sortCoordinates provides a function to correct the coordinate area, such -// correct C1:B3 to B1:C3. -func sortCoordinates(coordinates []int) error { - if len(coordinates) != 4 { - return ErrCoordinates - } - if coordinates[2] < coordinates[0] { - coordinates[2], coordinates[0] = coordinates[0], coordinates[2] - } - if coordinates[3] < coordinates[1] { - coordinates[3], coordinates[1] = coordinates[1], coordinates[3] - } - return nil -} - -// coordinatesToAreaRef provides a function to convert a pair of coordinates -// to area reference. -func (f *File) coordinatesToAreaRef(coordinates []int) (string, error) { - if len(coordinates) != 4 { - return "", ErrCoordinates - } - firstCell, err := CoordinatesToCellName(coordinates[0], coordinates[1]) - if err != nil { - return "", err - } - lastCell, err := CoordinatesToCellName(coordinates[2], coordinates[3]) - if err != nil { - return "", err - } - return firstCell + ":" + lastCell, err -} - // adjustMergeCells provides a function to update merged cells when inserting // or deleting rows or columns. func (f *File) adjustMergeCells(ws *xlsxWorksheet, dir adjustDirection, num, offset int) error { diff --git a/adjust_test.go b/adjust_test.go index c4af38b13a17b6848ed1e9b61225e52e3e06ba19..ced091df03444ef32cf12afbcc8df16eb0daacd0 100644 --- a/adjust_test.go +++ b/adjust_test.go @@ -99,20 +99,3 @@ func TestAdjustCalcChain(t *testing.T) { f.CalcChain = nil assert.NoError(t, f.InsertCol("Sheet1", "A")) } - -func TestCoordinatesToAreaRef(t *testing.T) { - f := NewFile() - _, err := f.coordinatesToAreaRef([]int{}) - assert.EqualError(t, err, ErrCoordinates.Error()) - _, err = f.coordinatesToAreaRef([]int{1, -1, 1, 1}) - assert.EqualError(t, err, "invalid cell coordinates [1, -1]") - _, err = f.coordinatesToAreaRef([]int{1, 1, 1, -1}) - assert.EqualError(t, err, "invalid cell coordinates [1, -1]") - ref, err := f.coordinatesToAreaRef([]int{1, 1, 1, 1}) - assert.NoError(t, err) - assert.EqualValues(t, ref, "A1:A1") -} - -func TestSortCoordinates(t *testing.T) { - assert.EqualError(t, sortCoordinates(make([]int, 3)), ErrCoordinates.Error()) -} diff --git a/datavalidation.go b/datavalidation.go index 04dbe25319d9c61c9eaee7a18d74022810bc1830..d44d2b8f482e58d89c76eefbb6db93b7595c982c 100644 --- a/datavalidation.go +++ b/datavalidation.go @@ -258,9 +258,30 @@ func (f *File) DeleteDataValidation(sheet, sqref string) error { if ws.DataValidations == nil { return nil } + delCells, err := f.flatSqref(sqref) + if err != nil { + return err + } dv := ws.DataValidations for i := 0; i < len(dv.DataValidation); i++ { - if dv.DataValidation[i].Sqref == sqref { + applySqref := []string{} + colCells, err := f.flatSqref(dv.DataValidation[i].Sqref) + if err != nil { + return err + } + for col, cells := range delCells { + for _, cell := range cells { + idx := inCoordinates(colCells[col], cell) + if idx != -1 { + colCells[col] = append(colCells[col][:idx], colCells[col][idx+1:]...) + } + } + } + for _, col := range colCells { + applySqref = append(applySqref, f.squashSqref(col)...) + } + dv.DataValidation[i].Sqref = strings.Join(applySqref, " ") + if len(applySqref) == 0 { dv.DataValidation = append(dv.DataValidation[:i], dv.DataValidation[i+1:]...) i-- } @@ -271,3 +292,31 @@ func (f *File) DeleteDataValidation(sheet, sqref string) error { } return nil } + +// squashSqref generates cell reference sequence by given cells coordinates list. +func (f *File) squashSqref(cells [][]int) []string { + if len(cells) == 1 { + cell, _ := CoordinatesToCellName(cells[0][0], cells[0][1]) + return []string{cell} + } else if len(cells) == 0 { + return []string{} + } + l, r, res := 0, 0, []string{} + for i := 1; i < len(cells); i++ { + if cells[i][0] == cells[r][0] && cells[i][1]-cells[r][1] > 1 { + curr, _ := f.coordinatesToAreaRef(append(cells[l], cells[r]...)) + if l == r { + curr, _ = CoordinatesToCellName(cells[l][0], cells[l][1]) + } + res = append(res, curr) + l, r = i, i + } else { + r++ + } + } + curr, _ := f.coordinatesToAreaRef(append(cells[l], cells[r]...)) + if l == r { + curr, _ = CoordinatesToCellName(cells[l][0], cells[l][1]) + } + return append(res, curr) +} diff --git a/datavalidation_test.go b/datavalidation_test.go index 13a0053c3ac7844a1a27e0443e526a92118f9c3a..f0afe5f14308f3e002f4391105d6f8c2aa05280f 100644 --- a/datavalidation_test.go +++ b/datavalidation_test.go @@ -129,10 +129,36 @@ func TestDeleteDataValidation(t *testing.T) { assert.NoError(t, dvRange.SetRange(10, 20, DataValidationTypeWhole, DataValidationOperatorBetween)) dvRange.SetInput("input title", "input body") assert.NoError(t, f.AddDataValidation("Sheet1", dvRange)) - assert.NoError(t, f.DeleteDataValidation("Sheet1", "A1:B2")) + + dvRange.Sqref = "A1" + assert.NoError(t, f.AddDataValidation("Sheet1", dvRange)) + assert.NoError(t, f.DeleteDataValidation("Sheet1", "B1")) + assert.NoError(t, f.DeleteDataValidation("Sheet1", "A1")) + + dvRange.Sqref = "C2:C5" + assert.NoError(t, f.AddDataValidation("Sheet1", dvRange)) + assert.NoError(t, f.DeleteDataValidation("Sheet1", "C4")) + + dvRange = NewDataValidation(true) + dvRange.Sqref = "D2:D2 D3 D4" + assert.NoError(t, dvRange.SetRange(10, 20, DataValidationTypeWhole, DataValidationOperatorBetween)) + dvRange.SetInput("input title", "input body") + assert.NoError(t, f.AddDataValidation("Sheet1", dvRange)) + assert.NoError(t, f.DeleteDataValidation("Sheet1", "D3")) + assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDeleteDataValidation.xlsx"))) + dvRange.Sqref = "A" + assert.NoError(t, f.AddDataValidation("Sheet1", dvRange)) + assert.EqualError(t, f.DeleteDataValidation("Sheet1", "A1"), `cannot convert cell "A" to coordinates: invalid cell name "A"`) + + assert.EqualError(t, f.DeleteDataValidation("Sheet1", "A1:A"), `cannot convert cell "A" to coordinates: invalid cell name "A"`) + ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml") + assert.True(t, ok) + ws.(*xlsxWorksheet).DataValidations.DataValidation[0].Sqref = "A1:A" + assert.EqualError(t, f.DeleteDataValidation("Sheet1", "A1:B2"), `cannot convert cell "A" to coordinates: invalid cell name "A"`) + // Test delete data validation on no exists worksheet. assert.EqualError(t, f.DeleteDataValidation("SheetN", "A1:B2"), "sheet SheetN is not exist") } diff --git a/lib.go b/lib.go index df2af4a90302c79d2f85639c960cc3f9eaab32b8..7db14c4cd74e0358d100ef3f480c20f371927cc7 100644 --- a/lib.go +++ b/lib.go @@ -219,6 +219,114 @@ func CoordinatesToCellName(col, row int, abs ...bool) (string, error) { return sign + colname + sign + strconv.Itoa(row), err } +// areaRefToCoordinates provides a function to convert area reference to a +// pair of coordinates. +func (f *File) areaRefToCoordinates(ref string) ([]int, error) { + rng := strings.Split(strings.Replace(ref, "$", "", -1), ":") + if len(rng) < 2 { + return nil, ErrParameterInvalid + } + + return areaRangeToCoordinates(rng[0], rng[1]) +} + +// areaRangeToCoordinates provides a function to convert cell range to a +// pair of coordinates. +func areaRangeToCoordinates(firstCell, lastCell string) ([]int, error) { + coordinates := make([]int, 4) + var err error + coordinates[0], coordinates[1], err = CellNameToCoordinates(firstCell) + if err != nil { + return coordinates, err + } + coordinates[2], coordinates[3], err = CellNameToCoordinates(lastCell) + return coordinates, err +} + +// sortCoordinates provides a function to correct the coordinate area, such +// correct C1:B3 to B1:C3. +func sortCoordinates(coordinates []int) error { + if len(coordinates) != 4 { + return ErrCoordinates + } + if coordinates[2] < coordinates[0] { + coordinates[2], coordinates[0] = coordinates[0], coordinates[2] + } + if coordinates[3] < coordinates[1] { + coordinates[3], coordinates[1] = coordinates[1], coordinates[3] + } + return nil +} + +// coordinatesToAreaRef provides a function to convert a pair of coordinates +// to area reference. +func (f *File) coordinatesToAreaRef(coordinates []int) (string, error) { + if len(coordinates) != 4 { + return "", ErrCoordinates + } + firstCell, err := CoordinatesToCellName(coordinates[0], coordinates[1]) + if err != nil { + return "", err + } + lastCell, err := CoordinatesToCellName(coordinates[2], coordinates[3]) + if err != nil { + return "", err + } + return firstCell + ":" + lastCell, err +} + +// flatSqref convert reference sequence to cell coordinates list. +func (f *File) flatSqref(sqref string) (cells map[int][][]int, err error) { + var coordinates []int + cells = make(map[int][][]int) + for _, ref := range strings.Fields(sqref) { + rng := strings.Split(ref, ":") + switch len(rng) { + case 1: + var col, row int + col, row, err = CellNameToCoordinates(rng[0]) + if err != nil { + return + } + cells[col] = append(cells[col], []int{col, row}) + case 2: + if coordinates, err = f.areaRefToCoordinates(ref); err != nil { + return + } + _ = sortCoordinates(coordinates) + for c := coordinates[0]; c <= coordinates[2]; c++ { + for r := coordinates[1]; r <= coordinates[3]; r++ { + cells[c] = append(cells[c], []int{c, r}) + } + } + } + } + return +} + +// inCoordinates provides a method to check if an coordinate is present in +// coordinates array, and return the index of its location, otherwise +// return -1. +func inCoordinates(a [][]int, x []int) int { + for idx, n := range a { + if x[0] == n[0] && x[1] == n[1] { + return idx + } + } + return -1 +} + +// inStrSlice provides a method to check if an element is present in an array, +// and return the index of its location, otherwise return -1. +func inStrSlice(a []string, x string) int { + for idx, n := range a { + if x == n { + return idx + } + } + return -1 +} + // boolPtr returns a pointer to a bool with the given value. func boolPtr(b bool) *bool { return &b } diff --git a/lib_test.go b/lib_test.go index 315688fc171ae024799848524ef3c4f9b1175c37..2e0e5066dcfecfd174f0e1e14373fb3298b84b0f 100644 --- a/lib_test.go +++ b/lib_test.go @@ -211,6 +211,27 @@ func TestCoordinatesToCellName_Error(t *testing.T) { } } +func TestCoordinatesToAreaRef(t *testing.T) { + f := NewFile() + _, err := f.coordinatesToAreaRef([]int{}) + assert.EqualError(t, err, ErrCoordinates.Error()) + _, err = f.coordinatesToAreaRef([]int{1, -1, 1, 1}) + assert.EqualError(t, err, "invalid cell coordinates [1, -1]") + _, err = f.coordinatesToAreaRef([]int{1, 1, 1, -1}) + assert.EqualError(t, err, "invalid cell coordinates [1, -1]") + ref, err := f.coordinatesToAreaRef([]int{1, 1, 1, 1}) + assert.NoError(t, err) + assert.EqualValues(t, ref, "A1:A1") +} + +func TestSortCoordinates(t *testing.T) { + assert.EqualError(t, sortCoordinates(make([]int, 3)), ErrCoordinates.Error()) +} + +func TestInStrSlice(t *testing.T) { + assert.EqualValues(t, -1, inStrSlice([]string{}, "")) +} + func TestBytesReplace(t *testing.T) { s := []byte{0x01} assert.EqualValues(t, s, bytesReplace(s, []byte{}, []byte{}, 0)) diff --git a/pivotTable.go b/pivotTable.go index f6d6d2dfdfc625f495a0df1cd637dd0823cca794..07cf84cf7122b6edf043e0287276849b0af63ab5 100644 --- a/pivotTable.go +++ b/pivotTable.go @@ -459,17 +459,6 @@ func (f *File) addPivotDataFields(pt *xlsxPivotTableDefinition, opt *PivotTableO return err } -// inStrSlice provides a method to check if an element is present in an array, -// and return the index of its location, otherwise return -1. -func inStrSlice(a []string, x string) int { - for idx, n := range a { - if x == n { - return idx - } - } - return -1 -} - // inPivotTableField provides a method to check if an element is present in // pivot table fields list, and return the index of its location, otherwise // return -1. diff --git a/pivotTable_test.go b/pivotTable_test.go index bf6bb01eb52a571dcc9f5e99e1354f22990ecc3e..dbb82523c70989732851bd2f060f0636f4421569 100644 --- a/pivotTable_test.go +++ b/pivotTable_test.go @@ -301,10 +301,6 @@ func TestGetPivotFieldsOrder(t *testing.T) { assert.EqualError(t, err, "sheet SheetN is not exist") } -func TestInStrSlice(t *testing.T) { - assert.EqualValues(t, -1, inStrSlice([]string{}, "")) -} - func TestGetPivotTableFieldName(t *testing.T) { f := NewFile() f.getPivotTableFieldName("-", []PivotTableField{})