From b23e5a26df2a70102503fd73bf2f7ede38e189b8 Mon Sep 17 00:00:00 2001 From: "Jian Yu, Chen" <77830479+Zncl2222@users.noreply.github.com> Date: Mon, 30 Sep 2024 21:00:59 +0800 Subject: [PATCH] This closes #1076, add new function MoveSheet to support changing sheet order in the workbook (#1996) - Add unit tests --- sheet.go | 43 +++++++++++++++++++++++++++++++++++++++++++ sheet_test.go | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/sheet.go b/sheet.go index 7cb13e24c0..48cf88411e 100644 --- a/sheet.go +++ b/sheet.go @@ -600,6 +600,49 @@ func (f *File) DeleteSheet(sheet string) error { return err } +// MoveSheet moves a sheet to a specified position in the workbook. The function +// moves the source sheet before the target sheet. After moving, other sheets +// will be shifted to the left or right. If the sheet is already at the target +// position, the function will not perform any action. Not that this function +// will be ungroup all sheets after moving. For example, move Sheet2 before +// Sheet1: +// +// err := f.MoveSheet("Sheet2", "Sheet1") +func (f *File) MoveSheet(source, target string) error { + if strings.EqualFold(source, target) { + return nil + } + wb, err := f.workbookReader() + if err != nil { + return err + } + sourceIdx, err := f.GetSheetIndex(source) + if err != nil { + return err + } + targetIdx, err := f.GetSheetIndex(target) + if err != nil { + return err + } + if sourceIdx < 0 { + return ErrSheetNotExist{source} + } + if targetIdx < 0 { + return ErrSheetNotExist{target} + } + _ = f.UngroupSheets() + activeSheetName := f.GetSheetName(f.GetActiveSheetIndex()) + sourceSheet := wb.Sheets.Sheet[sourceIdx] + wb.Sheets.Sheet = append(wb.Sheets.Sheet[:sourceIdx], wb.Sheets.Sheet[sourceIdx+1:]...) + if targetIdx > sourceIdx { + targetIdx-- + } + wb.Sheets.Sheet = append(wb.Sheets.Sheet[:targetIdx], append([]xlsxSheet{sourceSheet}, wb.Sheets.Sheet[targetIdx:]...)...) + activeSheetIdx, _ := f.GetSheetIndex(activeSheetName) + f.SetActiveSheet(activeSheetIdx) + return err +} + // deleteAndAdjustDefinedNames delete and adjust defined name in the workbook // by given worksheet ID. func deleteAndAdjustDefinedNames(wb *xlsxWorkbook, deleteLocalSheetID int) { diff --git a/sheet_test.go b/sheet_test.go index 1c4f55acf2..47fcd97e46 100644 --- a/sheet_test.go +++ b/sheet_test.go @@ -547,6 +547,43 @@ func TestDeleteSheet(t *testing.T) { assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDeleteSheet2.xlsx"))) } +func TestMoveSheet(t *testing.T) { + f := NewFile() + defer f.Close() + for i := 2; i < 6; i++ { + _, err := f.NewSheet("Sheet" + strconv.Itoa(i)) + assert.NoError(t, err) + } + assert.Equal(t, []string{"Sheet1", "Sheet2", "Sheet3", "Sheet4", "Sheet5"}, f.GetSheetList()) + + // Move target to first position + assert.NoError(t, f.MoveSheet("Sheet2", "Sheet1")) + assert.Equal(t, []string{"Sheet2", "Sheet1", "Sheet3", "Sheet4", "Sheet5"}, f.GetSheetList()) + assert.Equal(t, "Sheet1", f.GetSheetName(f.GetActiveSheetIndex())) + + // Move target to last position + assert.NoError(t, f.MoveSheet("Sheet2", "Sheet5")) + assert.NoError(t, f.MoveSheet("Sheet5", "Sheet2")) + assert.Equal(t, []string{"Sheet1", "Sheet3", "Sheet4", "Sheet5", "Sheet2"}, f.GetSheetList()) + + // Move target to same position + assert.NoError(t, f.MoveSheet("Sheet1", "Sheet1")) + assert.Equal(t, []string{"Sheet1", "Sheet3", "Sheet4", "Sheet5", "Sheet2"}, f.GetSheetList()) + + // Test move sheet with invalid sheet name + assert.Equal(t, ErrSheetNameBlank, f.MoveSheet("", "Sheet2")) + assert.Equal(t, ErrSheetNameBlank, f.MoveSheet("Sheet1", "")) + + // Test move sheet on not exists worksheet + assert.Equal(t, ErrSheetNotExist{"SheetN"}, f.MoveSheet("SheetN", "Sheet2")) + assert.Equal(t, ErrSheetNotExist{"SheetN"}, f.MoveSheet("Sheet1", "SheetN")) + + // Test move sheet with unsupported workbook charset + f.WorkBook = nil + f.Pkg.Store("xl/workbook.xml", MacintoshCyrillicCharset) + assert.EqualError(t, f.MoveSheet("Sheet2", "Sheet1"), "XML syntax error on line 1: invalid UTF-8") +} + func TestDeleteAndAdjustDefinedNames(t *testing.T) { deleteAndAdjustDefinedNames(nil, 0) deleteAndAdjustDefinedNames(&xlsxWorkbook{}, 0)