Skip to content

Commit

Permalink
This closes qax-os#1729, support copy conditional format and data val…
Browse files Browse the repository at this point in the history
…idation on duplicate row (qax-os#1733)
  • Loading branch information
bramvbilsen authored Nov 28, 2023
1 parent bce2789 commit 866e7fd
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 10 deletions.
90 changes: 82 additions & 8 deletions rows.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand All @@ -637,7 +651,7 @@ func (f *File) DuplicateRowTo(sheet string, row, row2 int) error {
}

if !ok {
return nil
return err
}

idx2 := -1
Expand All @@ -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)
Expand All @@ -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
}
Expand Down
43 changes: 41 additions & 2 deletions rows_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -878,13 +878,52 @@ 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)
assert.Equal(t, "Amount+C10", formula)
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) {
Expand All @@ -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) {
Expand Down

0 comments on commit 866e7fd

Please sign in to comment.