diff --git a/excelize_test.go b/excelize_test.go index 697d6497bf..7eb689fd21 100644 --- a/excelize_test.go +++ b/excelize_test.go @@ -1607,155 +1607,6 @@ func TestAttrValToInt(t *testing.T) { assert.EqualError(t, err, `strconv.Atoi: parsing "s": invalid syntax`) } -func TestMoveSheet(t *testing.T) { - f := NewFile() - - for i := 2; i < 6; i++ { - f.NewSheet("Sheet" + strconv.Itoa(i)) - } - - sheetList := f.GetSheetList() - assert.Equal(t, []string{"Sheet1", "Sheet2", "Sheet3", "Sheet4", "Sheet5"}, sheetList) - - // Move target to first position - err := f.MoveSheet(1, 0) - sheetList = f.GetSheetList() - assert.Equal(t, []string{"Sheet2", "Sheet1", "Sheet3", "Sheet4", "Sheet5"}, sheetList) - assert.NoError(t, err) - ws, _ := f.workSheetReader("Sheet1") - assert.Equal(t, ws.SheetViews.SheetView[0].TabSelected, false) - - // Move target to last position - err = f.MoveSheet(0, f.SheetCount-1) - sheetList = f.GetSheetList() - assert.Equal(t, []string{"Sheet1", "Sheet3", "Sheet4", "Sheet5", "Sheet2"}, sheetList) - assert.NoError(t, err) - - // Move target to same position - err = f.MoveSheet(4, 4) - sheetList = f.GetSheetList() - assert.Equal(t, []string{"Sheet1", "Sheet3", "Sheet4", "Sheet5", "Sheet2"}, sheetList) - assert.NoError(t, err) - - // Move target to non start and end position - err = f.MoveSheet(4, 2) - sheetList = f.GetSheetList() - assert.Equal(t, []string{"Sheet1", "Sheet3", "Sheet2", "Sheet4", "Sheet5"}, sheetList) - assert.NoError(t, err) - - err = f.MoveSheet(4, 1) - sheetList = f.GetSheetList() - assert.Equal(t, []string{"Sheet1", "Sheet5", "Sheet3", "Sheet2", "Sheet4"}, sheetList) - assert.NoError(t, err) - - // MoveSheets and test grouping - err = f.MoveSheet(3, 2) - sheetList = f.GetSheetList() - assert.Equal(t, []string{"Sheet1", "Sheet5", "Sheet2", "Sheet3", "Sheet4"}, sheetList) - assert.NoError(t, err) - f.SetActiveSheet(2) - f.GroupSheets([]string{"Sheet2", "Sheet3"}) - for _, sheet := range []string{"Sheet2", "Sheet3"} { - ws, _ := f.workSheetReader(sheet) - assert.Equal(t, ws.SheetViews.SheetView[0].TabSelected, true) - } - - // Test Moving the ChartSheet - err = prepareTestChartSheet(f) - assert.NoError(t, err) - sheetList = f.GetSheetList() - assert.Equal(t, []string{"Sheet1", "Sheet5", "Sheet2", "Sheet3", "Sheet4", "ChartSheet"}, sheetList) - err = f.MoveSheet(5, 2) - assert.NoError(t, err) - sheetList = f.GetSheetList() - assert.Equal(t, []string{"Sheet1", "Sheet5", "ChartSheet", "Sheet2", "Sheet3", "Sheet4"}, sheetList) - // Test Moving the Active ChartSheet - f.SetActiveSheet(2) - err = f.MoveSheet(2, 3) - assert.NoError(t, err) - - // Move With Error Index - assert.Error(t, f.MoveSheet(0, -1), ErrSheetIdx) - assert.Error(t, f.MoveSheet(1, f.SheetCount), ErrSheetIdx) - assert.Error(t, f.MoveSheet(-1, 0), ErrSheetIdx) - assert.Error(t, f.MoveSheet(f.SheetCount, 0), ErrSheetIdx) - - // Test Move with error Workbook - f.WorkBook = nil - f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) - assert.EqualError(t, f.MoveSheet(0, 1), "XML syntax error on line 1: invalid UTF-8") -} - -func TestSwapSheets(t *testing.T) { - f := NewFile() - - for i := 2; i < 6; i++ { - f.NewSheet("Sheet" + strconv.Itoa(i)) - } - - sheetList := f.GetSheetList() - assert.Equal(t, []string{"Sheet1", "Sheet2", "Sheet3", "Sheet4", "Sheet5"}, sheetList) - - err := f.SwapSheets(0, 1) - sheetList = f.GetSheetList() - assert.Equal(t, []string{"Sheet2", "Sheet1", "Sheet3", "Sheet4", "Sheet5"}, sheetList) - assert.NoError(t, err) - - err = f.SwapSheets(1, 4) - sheetList = f.GetSheetList() - assert.Equal(t, []string{"Sheet2", "Sheet5", "Sheet3", "Sheet4", "Sheet1"}, sheetList) - assert.NoError(t, err) - - err = f.SwapSheets(3, 2) - sheetList = f.GetSheetList() - assert.Equal(t, []string{"Sheet2", "Sheet5", "Sheet4", "Sheet3", "Sheet1"}, sheetList) - assert.NoError(t, err) - - err = f.SwapSheets(0, f.SheetCount-1) - sheetList = f.GetSheetList() - assert.Equal(t, []string{"Sheet1", "Sheet5", "Sheet4", "Sheet3", "Sheet2"}, sheetList) - assert.NoError(t, err) - - // Test SwapSheets with same index - err = f.SwapSheets(0, 0) - sheetList = f.GetSheetList() - assert.Equal(t, []string{"Sheet1", "Sheet5", "Sheet4", "Sheet3", "Sheet2"}, sheetList) - assert.NoError(t, err) - - // Swap sheet and test grouping - err = f.SwapSheets(3, 2) - sheetList = f.GetSheetList() - assert.Equal(t, []string{"Sheet1", "Sheet5", "Sheet3", "Sheet4", "Sheet2"}, sheetList) - assert.NoError(t, err) - f.SetActiveSheet(2) - f.GroupSheets([]string{"Sheet2", "Sheet3"}) - for _, sheet := range []string{"Sheet2", "Sheet3"} { - ws, _ := f.workSheetReader(sheet) - assert.Equal(t, ws.SheetViews.SheetView[0].TabSelected, true) - } - - // Test Swapping the ChartSheet - err = prepareTestChartSheet(f) - assert.NoError(t, err) - sheetList = f.GetSheetList() - assert.Equal(t, []string{"Sheet1", "Sheet5", "Sheet3", "Sheet4", "Sheet2", "ChartSheet"}, sheetList) - err = f.SwapSheets(5, 2) - assert.NoError(t, err) - sheetList = f.GetSheetList() - assert.Equal(t, []string{"Sheet1", "Sheet5", "ChartSheet", "Sheet4", "Sheet2", "Sheet3"}, sheetList) - - // Swap With Error index - assert.Error(t, f.SwapSheets(-1, 0), ErrSheetIdx) - assert.Error(t, f.SwapSheets(0, -1), ErrSheetIdx) - assert.Error(t, f.SwapSheets(f.SheetCount, 0), ErrSheetIdx) - assert.Error(t, f.SwapSheets(0, f.SheetCount), ErrSheetIdx) - - // Test Swap with error Workbook - f.WorkBook = nil - f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) - assert.EqualError(t, f.SwapSheets(0, 1), "XML syntax error on line 1: invalid UTF-8") -} - func prepareTestBook1() (*File, error) { f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) if err != nil { @@ -1861,45 +1712,6 @@ func prepareTestBook5(opts Options) (*File, error) { return f, nil } -func prepareTestChartSheet(f *File) error { - for idx, row := range [][]interface{}{ - {nil, "Apple", "Orange", "Pear"}, - {"Small", 2, 3, 3}, - {"Normal", 5, 2, 4}, - {"Large", 6, 7, 8}, - } { - cell, err := CoordinatesToCellName(1, idx+1) - if err != nil { - return err - } - if err := f.SetSheetRow("Sheet1", cell, &row); err != nil { - return err - } - } - if err := f.AddChartSheet("ChartSheet", &Chart{ - Type: Col, - Series: []ChartSeries{ - { - Name: "Sheet1!$A$2", - Categories: "Sheet1!$B$1:$D$1", - Values: "Sheet1!$B$2:$D$2", - }, - }, - }, &Chart{ - Type: Line, - Series: []ChartSeries{ - { - Name: "Sheet1!$A$4", - Categories: "Sheet1!$B$1:$D$1", - Values: "Sheet1!$B$4:$D$4", - }, - }, - }); err != nil { - return err - } - return nil -} - func fillCells(f *File, sheet string, colCount, rowCount int) error { for col := 1; col <= colCount; col++ { for row := 1; row <= rowCount; row++ { diff --git a/sheet.go b/sheet.go index 7cb13e24c0..9e58b566f1 100644 --- a/sheet.go +++ b/sheet.go @@ -600,6 +600,46 @@ 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. +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) diff --git a/workbook.go b/workbook.go index d02e1c5377..44db6c9add 100644 --- a/workbook.go +++ b/workbook.go @@ -130,89 +130,6 @@ func (f *File) UnprotectWorkbook(password ...string) error { return err } -// unselectSheetTab deselects the sheet tab at the specified sheet index -// if it is currently active. -func (f *File) unselectSheetTab(sheetIdx int, wb *xlsxWorkbook) { - activeSheetName := f.GetSheetName(f.GetActiveSheetIndex()) - if activeSheetName == wb.Sheets.Sheet[sheetIdx].Name { - ws, err := f.workSheetReader(wb.Sheets.Sheet[sheetIdx].Name) - if err != nil { - return - } - ws.SheetViews.SheetView[0].TabSelected = false - } -} - -// SwapSheets swaps the position of two sheets in the workbook -// based on their index positions. If the indices of the sheets -// are the same, the function does nothing. Note that the index -// must be greater than or equal to 0 and less than SheetCount. -// For example: -// -// err := f.SwapSheets(1, 3) -// if err != nil { -// // handle the error -// } -func (f *File) SwapSheets(sheet1Idx, sheet2Idx int) error { - if sheet1Idx == sheet2Idx { - return nil - } - - wb, err := f.workbookReader() - if err != nil { - return err - } - if sheet1Idx >= f.SheetCount || sheet2Idx >= f.SheetCount || sheet1Idx < 0 || sheet2Idx < 0 { - return ErrSheetIdx - } - - // Unselect the tab of a sheet by index - f.unselectSheetTab(sheet1Idx, wb) - f.unselectSheetTab(sheet2Idx, wb) - - tempSheet := wb.Sheets.Sheet[sheet1Idx] - wb.Sheets.Sheet[sheet1Idx] = wb.Sheets.Sheet[sheet2Idx] - wb.Sheets.Sheet[sheet2Idx] = tempSheet - return nil -} - -// MoveSheet moves a worksheet to a specified position in the workbook. -// The function takes the index of the source sheet and the target index. -// After moving the worksheet to the target index, 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. Note that the index must be -// greater than or equal to 0 and less than SheetCount. -// For example: -// -// err := f.MoveSheet(0, 3) -// if err != nil { -// // handle the error -// } -func (f *File) MoveSheet(sourceIdx, targetIdx int) error { - if sourceIdx == targetIdx { - return nil - } - wb, err := f.workbookReader() - if err != nil { - return err - } - - if sourceIdx >= f.SheetCount || sourceIdx < 0 || targetIdx >= f.SheetCount || targetIdx < 0 { - return ErrSheetIdx - } - - // Unselect the tab of a sheet by index - f.unselectSheetTab(targetIdx, wb) - f.unselectSheetTab(sourceIdx, wb) - - sourceSheet := wb.Sheets.Sheet[sourceIdx] - wb.Sheets.Sheet = append(wb.Sheets.Sheet[:sourceIdx], wb.Sheets.Sheet[sourceIdx+1:]...) - sheetsTemp := append([]xlsxSheet{sourceSheet}, wb.Sheets.Sheet[targetIdx:]...) - wb.Sheets.Sheet = append(wb.Sheets.Sheet[:targetIdx], sheetsTemp...) - - return nil -} - // setWorkbook update workbook property of the spreadsheet. Maximum 31 // characters are allowed in sheet title. func (f *File) setWorkbook(name string, sheetID, rid int) {