diff --git a/rows.go b/rows.go index 9c240570ee..b887c96628 100644 --- a/rows.go +++ b/rows.go @@ -18,10 +18,24 @@ import ( "math" "os" "strconv" + "strings" "github.com/mohae/deepcopy" ) +// duplicateHelperFunc defines functions to duplicate helper. +var duplicateHelperFunc = [3]func(*File, *xlsxWorksheet, string, int, int) error{ + func(f *File, ws *xlsxWorksheet, sheet string, row, row2 int) error { + return f.duplicateConditionalFormat(ws, sheet, row, row2) + }, + func(f *File, ws *xlsxWorksheet, sheet string, row, row2 int) error { + return f.duplicateDataValidations(ws, sheet, row, row2) + }, + func(f *File, ws *xlsxWorksheet, sheet string, row, row2 int) error { + return f.duplicateMergeCells(ws, sheet, row, row2) + }, +} + // GetRows return all the rows in a sheet by given worksheet name, returned as // a two-dimensional array, where the value of the cell is converted to the // string type. If the cell format can be applied to the value of the cell, @@ -618,7 +632,7 @@ func (f *File) DuplicateRowTo(sheet string, row, row2 int) error { } if row2 < 1 || row == row2 { - return nil + return err } var ok bool @@ -637,7 +651,7 @@ func (f *File) DuplicateRowTo(sheet string, row, row2 int) error { } if !ok { - return nil + return err } idx2 := -1 @@ -647,10 +661,6 @@ func (f *File) DuplicateRowTo(sheet string, row, row2 int) error { break } } - if idx2 == -1 && len(ws.SheetData.Row) >= row2 { - return nil - } - rowCopy.C = append(make([]xlsxC, 0, len(rowCopy.C)), rowCopy.C...) rowCopy.adjustSingleRowDimensions(row2 - row) _ = f.adjustSingleRowFormulas(sheet, sheet, &rowCopy, row, row2-row, true) @@ -660,12 +670,76 @@ func (f *File) DuplicateRowTo(sheet string, row, row2 int) error { } else { ws.SheetData.Row = append(ws.SheetData.Row, rowCopy) } - return f.duplicateMergeCells(sheet, ws, row, row2) + for _, fn := range duplicateHelperFunc { + if err := fn(f, ws, sheet, row, row2); err != nil { + return err + } + } + return err +} + +// duplicateConditionalFormat create conditional formatting for the destination +// row if there are conditional formats in the copied row. +func (f *File) duplicateConditionalFormat(ws *xlsxWorksheet, sheet string, row, row2 int) error { + var cfs []*xlsxConditionalFormatting + for _, cf := range ws.ConditionalFormatting { + if cf != nil { + if !strings.Contains(cf.SQRef, ":") { + cf.SQRef += ":" + cf.SQRef + } + abs := strings.Contains(cf.SQRef, "$") + coordinates, err := rangeRefToCoordinates(cf.SQRef) + if err != nil { + return err + } + x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3] + if y1 == y2 && y1 == row { + cfCopy := deepcopy.Copy(*cf).(xlsxConditionalFormatting) + if cfCopy.SQRef, err = f.coordinatesToRangeRef([]int{x1, row2, x2, row2}, abs); err != nil { + return err + } + cfs = append(cfs, &cfCopy) + } + } + } + ws.ConditionalFormatting = append(ws.ConditionalFormatting, cfs...) + return nil +} + +// duplicateDataValidations create data validations for the destination row if +// there are data validation rules in the copied row. +func (f *File) duplicateDataValidations(ws *xlsxWorksheet, sheet string, row, row2 int) error { + if ws.DataValidations == nil { + return nil + } + var dvs []*xlsxDataValidation + for _, dv := range ws.DataValidations.DataValidation { + if dv != nil { + if !strings.Contains(dv.Sqref, ":") { + dv.Sqref += ":" + dv.Sqref + } + abs := strings.Contains(dv.Sqref, "$") + coordinates, err := rangeRefToCoordinates(dv.Sqref) + if err != nil { + return err + } + x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3] + if y1 == y2 && y1 == row { + dvCopy := deepcopy.Copy(*dv).(xlsxDataValidation) + if dvCopy.Sqref, err = f.coordinatesToRangeRef([]int{x1, row2, x2, row2}, abs); err != nil { + return err + } + dvs = append(dvs, &dvCopy) + } + } + } + ws.DataValidations.DataValidation = append(ws.DataValidations.DataValidation, dvs...) + return nil } // duplicateMergeCells merge cells in the destination row if there are single // row merged cells in the copied row. -func (f *File) duplicateMergeCells(sheet string, ws *xlsxWorksheet, row, row2 int) error { +func (f *File) duplicateMergeCells(ws *xlsxWorksheet, sheet string, row, row2 int) error { if ws.MergeCells == nil { return nil } diff --git a/rows_test.go b/rows_test.go index 3e49580293..8fd0bfc5b4 100644 --- a/rows_test.go +++ b/rows_test.go @@ -878,6 +878,23 @@ func TestDuplicateRow(t *testing.T) { })) assert.NoError(t, f.SetCellFormula("Sheet1", "A1", "Amount+C1")) assert.NoError(t, f.SetCellValue("Sheet1", "A10", "A10")) + + format, err := f.NewConditionalStyle(&Style{Font: &Font{Color: "9A0511"}, Fill: Fill{Type: "pattern", Color: []string{"FEC7CE"}, Pattern: 1}}) + assert.NoError(t, err) + + expected := []ConditionalFormatOptions{ + {Type: "cell", Criteria: "greater than", Format: format, Value: "0"}, + } + assert.NoError(t, f.SetConditionalFormat("Sheet1", "A1", expected)) + + dv := NewDataValidation(true) + dv.Sqref = "A1" + assert.NoError(t, dv.SetDropList([]string{"1", "2", "3"})) + assert.NoError(t, f.AddDataValidation("Sheet1", dv)) + ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml") + assert.True(t, ok) + ws.(*xlsxWorksheet).DataValidations.DataValidation[0].Sqref = "A1" + assert.NoError(t, f.DuplicateRowTo("Sheet1", 1, 10)) formula, err := f.GetCellFormula("Sheet1", "A10") assert.NoError(t, err) @@ -885,6 +902,28 @@ func TestDuplicateRow(t *testing.T) { value, err := f.GetCellValue("Sheet1", "A11") assert.NoError(t, err) assert.Equal(t, "A10", value) + + cfs, err := f.GetConditionalFormats("Sheet1") + assert.NoError(t, err) + assert.Len(t, cfs, 2) + assert.Equal(t, expected, cfs["A10:A10"]) + + dvs, err := f.GetDataValidations("Sheet1") + assert.NoError(t, err) + assert.Len(t, dvs, 2) + assert.Equal(t, "A10:A10", dvs[1].Sqref) + + // Test duplicate data validation with row number exceeds maximum limit + assert.Equal(t, ErrMaxRows, f.duplicateDataValidations(ws.(*xlsxWorksheet), "Sheet1", 1, TotalRows+1)) + // Test duplicate data validation with invalid range reference + ws.(*xlsxWorksheet).DataValidations.DataValidation[0].Sqref = "A" + assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), f.duplicateDataValidations(ws.(*xlsxWorksheet), "Sheet1", 1, 10)) + + // Test duplicate conditional formatting with row number exceeds maximum limit + assert.Equal(t, ErrMaxRows, f.duplicateConditionalFormat(ws.(*xlsxWorksheet), "Sheet1", 1, TotalRows+1)) + // Test duplicate conditional formatting with invalid range reference + ws.(*xlsxWorksheet).ConditionalFormatting[0].SQRef = "A" + assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), f.duplicateConditionalFormat(ws.(*xlsxWorksheet), "Sheet1", 1, 10)) } func TestDuplicateRowTo(t *testing.T) { @@ -911,9 +950,9 @@ func TestDuplicateMergeCells(t *testing.T) { ws := &xlsxWorksheet{MergeCells: &xlsxMergeCells{ Cells: []*xlsxMergeCell{{Ref: "A1:-"}}, }} - assert.EqualError(t, f.duplicateMergeCells("Sheet1", ws, 0, 0), `cannot convert cell "-" to coordinates: invalid cell name "-"`) + assert.EqualError(t, f.duplicateMergeCells(ws, "Sheet1", 0, 0), `cannot convert cell "-" to coordinates: invalid cell name "-"`) ws.MergeCells.Cells[0].Ref = "A1:B1" - assert.EqualError(t, f.duplicateMergeCells("SheetN", ws, 1, 2), "sheet SheetN does not exist") + assert.EqualError(t, f.duplicateMergeCells(ws, "SheetN", 1, 2), "sheet SheetN does not exist") } func TestGetValueFromInlineStr(t *testing.T) {