This closes #979, fix the data validation deletion issue and tidy the internal function in the source code
This commit is contained in:
parent
933159f939
commit
cf9fbafdd8
60
adjust.go
60
adjust.go
|
@ -11,10 +11,6 @@
|
||||||
|
|
||||||
package excelize
|
package excelize
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type adjustDirection bool
|
type adjustDirection bool
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -194,62 +190,6 @@ func (f *File) adjustAutoFilterHelper(dir adjustDirection, coordinates []int, nu
|
||||||
return coordinates
|
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
|
// adjustMergeCells provides a function to update merged cells when inserting
|
||||||
// or deleting rows or columns.
|
// or deleting rows or columns.
|
||||||
func (f *File) adjustMergeCells(ws *xlsxWorksheet, dir adjustDirection, num, offset int) error {
|
func (f *File) adjustMergeCells(ws *xlsxWorksheet, dir adjustDirection, num, offset int) error {
|
||||||
|
|
|
@ -99,20 +99,3 @@ func TestAdjustCalcChain(t *testing.T) {
|
||||||
f.CalcChain = nil
|
f.CalcChain = nil
|
||||||
assert.NoError(t, f.InsertCol("Sheet1", "A"))
|
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())
|
|
||||||
}
|
|
||||||
|
|
|
@ -258,9 +258,30 @@ func (f *File) DeleteDataValidation(sheet, sqref string) error {
|
||||||
if ws.DataValidations == nil {
|
if ws.DataValidations == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
delCells, err := f.flatSqref(sqref)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
dv := ws.DataValidations
|
dv := ws.DataValidations
|
||||||
for i := 0; i < len(dv.DataValidation); i++ {
|
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:]...)
|
dv.DataValidation = append(dv.DataValidation[:i], dv.DataValidation[i+1:]...)
|
||||||
i--
|
i--
|
||||||
}
|
}
|
||||||
|
@ -271,3 +292,31 @@ func (f *File) DeleteDataValidation(sheet, sqref string) error {
|
||||||
}
|
}
|
||||||
return nil
|
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)
|
||||||
|
}
|
||||||
|
|
|
@ -129,10 +129,36 @@ func TestDeleteDataValidation(t *testing.T) {
|
||||||
assert.NoError(t, dvRange.SetRange(10, 20, DataValidationTypeWhole, DataValidationOperatorBetween))
|
assert.NoError(t, dvRange.SetRange(10, 20, DataValidationTypeWhole, DataValidationOperatorBetween))
|
||||||
dvRange.SetInput("input title", "input body")
|
dvRange.SetInput("input title", "input body")
|
||||||
assert.NoError(t, f.AddDataValidation("Sheet1", dvRange))
|
assert.NoError(t, f.AddDataValidation("Sheet1", dvRange))
|
||||||
|
|
||||||
assert.NoError(t, f.DeleteDataValidation("Sheet1", "A1:B2"))
|
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")))
|
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.
|
// Test delete data validation on no exists worksheet.
|
||||||
assert.EqualError(t, f.DeleteDataValidation("SheetN", "A1:B2"), "sheet SheetN is not exist")
|
assert.EqualError(t, f.DeleteDataValidation("SheetN", "A1:B2"), "sheet SheetN is not exist")
|
||||||
}
|
}
|
||||||
|
|
108
lib.go
108
lib.go
|
@ -219,6 +219,114 @@ func CoordinatesToCellName(col, row int, abs ...bool) (string, error) {
|
||||||
return sign + colname + sign + strconv.Itoa(row), err
|
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.
|
// boolPtr returns a pointer to a bool with the given value.
|
||||||
func boolPtr(b bool) *bool { return &b }
|
func boolPtr(b bool) *bool { return &b }
|
||||||
|
|
||||||
|
|
21
lib_test.go
21
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) {
|
func TestBytesReplace(t *testing.T) {
|
||||||
s := []byte{0x01}
|
s := []byte{0x01}
|
||||||
assert.EqualValues(t, s, bytesReplace(s, []byte{}, []byte{}, 0))
|
assert.EqualValues(t, s, bytesReplace(s, []byte{}, []byte{}, 0))
|
||||||
|
|
|
@ -459,17 +459,6 @@ func (f *File) addPivotDataFields(pt *xlsxPivotTableDefinition, opt *PivotTableO
|
||||||
return err
|
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
|
// inPivotTableField provides a method to check if an element is present in
|
||||||
// pivot table fields list, and return the index of its location, otherwise
|
// pivot table fields list, and return the index of its location, otherwise
|
||||||
// return -1.
|
// return -1.
|
||||||
|
|
|
@ -301,10 +301,6 @@ func TestGetPivotFieldsOrder(t *testing.T) {
|
||||||
assert.EqualError(t, err, "sheet SheetN is not exist")
|
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) {
|
func TestGetPivotTableFieldName(t *testing.T) {
|
||||||
f := NewFile()
|
f := NewFile()
|
||||||
f.getPivotTableFieldName("-", []PivotTableField{})
|
f.getPivotTableFieldName("-", []PivotTableField{})
|
||||||
|
|
Loading…
Reference in New Issue