Skip to content

Commit 239697b

Browse files
committed
Add MoveSheet and SwapSheets functions to support changing sheet order in the workbook (#1076)
- Add MoveSheet and SwapSheets functions to change sheet order in the workbook - Add unit tests for MoveSheet and SwapSheets functions
1 parent 41c7dd3 commit 239697b

File tree

2 files changed

+269
-0
lines changed

2 files changed

+269
-0
lines changed

excelize_test.go

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1607,6 +1607,153 @@ func TestAttrValToInt(t *testing.T) {
16071607
assert.EqualError(t, err, `strconv.Atoi: parsing "s": invalid syntax`)
16081608
}
16091609

1610+
func TestMoveSheet(t *testing.T) {
1611+
f := NewFile()
1612+
1613+
for i := 2; i < 6; i++ {
1614+
f.NewSheet("Sheet" + strconv.Itoa(i))
1615+
}
1616+
1617+
sheetList := f.GetSheetList()
1618+
assert.Equal(t, []string{"Sheet1", "Sheet2", "Sheet3", "Sheet4", "Sheet5"}, sheetList)
1619+
1620+
// Move target to first position
1621+
err := f.MoveSheet(1, 0)
1622+
sheetList = f.GetSheetList()
1623+
assert.Equal(t, []string{"Sheet2", "Sheet1", "Sheet3", "Sheet4", "Sheet5"}, sheetList)
1624+
assert.NoError(t, err)
1625+
ws, _ := f.workSheetReader("Sheet1")
1626+
assert.Equal(t, ws.SheetViews.SheetView[0].TabSelected, false)
1627+
1628+
// Move target to last position
1629+
err = f.MoveSheet(0, f.SheetCount-1)
1630+
sheetList = f.GetSheetList()
1631+
assert.Equal(t, []string{"Sheet1", "Sheet3", "Sheet4", "Sheet5", "Sheet2"}, sheetList)
1632+
assert.NoError(t, err)
1633+
1634+
// Move target to same position
1635+
err = f.MoveSheet(4, 4)
1636+
sheetList = f.GetSheetList()
1637+
assert.Equal(t, []string{"Sheet1", "Sheet3", "Sheet4", "Sheet5", "Sheet2"}, sheetList)
1638+
assert.NoError(t, err)
1639+
1640+
// Move target to non start and end position
1641+
err = f.MoveSheet(4, 2)
1642+
sheetList = f.GetSheetList()
1643+
assert.Equal(t, []string{"Sheet1", "Sheet3", "Sheet2", "Sheet4", "Sheet5"}, sheetList)
1644+
assert.NoError(t, err)
1645+
1646+
err = f.MoveSheet(4, 1)
1647+
sheetList = f.GetSheetList()
1648+
assert.Equal(t, []string{"Sheet1", "Sheet5", "Sheet3", "Sheet2", "Sheet4"}, sheetList)
1649+
assert.NoError(t, err)
1650+
1651+
// MoveSheets and test grouping
1652+
err = f.MoveSheet(3, 2)
1653+
sheetList = f.GetSheetList()
1654+
assert.Equal(t, []string{"Sheet1", "Sheet5", "Sheet2", "Sheet3", "Sheet4"}, sheetList)
1655+
assert.NoError(t, err)
1656+
f.SetActiveSheet(2)
1657+
f.GroupSheets([]string{"Sheet2", "Sheet3"})
1658+
for _, sheet := range []string{"Sheet2", "Sheet3"} {
1659+
ws, _ := f.workSheetReader(sheet)
1660+
assert.Equal(t, ws.SheetViews.SheetView[0].TabSelected, true)
1661+
}
1662+
1663+
// Test Moving the ChartSheet
1664+
err = prepareTestChartSheet(f)
1665+
assert.NoError(t, err)
1666+
sheetList = f.GetSheetList()
1667+
assert.Equal(t, []string{"Sheet1", "Sheet5", "Sheet2", "Sheet3", "Sheet4", "ChartSheet"}, sheetList)
1668+
err = f.MoveSheet(5, 2)
1669+
assert.NoError(t, err)
1670+
sheetList = f.GetSheetList()
1671+
assert.Equal(t, []string{"Sheet1", "Sheet5", "ChartSheet", "Sheet2", "Sheet3", "Sheet4"}, sheetList)
1672+
// Test Moving the Active ChartSheet
1673+
f.SetActiveSheet(2)
1674+
err = f.MoveSheet(2, 3)
1675+
assert.NoError(t, err)
1676+
1677+
// Move With Error Index
1678+
assert.Error(t, f.MoveSheet(0, -1), ErrSheetIdx)
1679+
assert.Error(t, f.MoveSheet(1, f.SheetCount), ErrSheetIdx)
1680+
1681+
// Test Move with error Workbook
1682+
f.WorkBook = nil
1683+
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
1684+
assert.EqualError(t, f.MoveSheet(0, 1), "XML syntax error on line 1: invalid UTF-8")
1685+
}
1686+
1687+
func TestSwapSheets(t *testing.T) {
1688+
f := NewFile()
1689+
1690+
for i := 2; i < 6; i++ {
1691+
f.NewSheet("Sheet" + strconv.Itoa(i))
1692+
}
1693+
1694+
sheetList := f.GetSheetList()
1695+
assert.Equal(t, []string{"Sheet1", "Sheet2", "Sheet3", "Sheet4", "Sheet5"}, sheetList)
1696+
1697+
err := f.SwapSheets(0, 1)
1698+
sheetList = f.GetSheetList()
1699+
assert.Equal(t, []string{"Sheet2", "Sheet1", "Sheet3", "Sheet4", "Sheet5"}, sheetList)
1700+
assert.NoError(t, err)
1701+
1702+
err = f.SwapSheets(1, 4)
1703+
sheetList = f.GetSheetList()
1704+
assert.Equal(t, []string{"Sheet2", "Sheet5", "Sheet3", "Sheet4", "Sheet1"}, sheetList)
1705+
assert.NoError(t, err)
1706+
1707+
err = f.SwapSheets(3, 2)
1708+
sheetList = f.GetSheetList()
1709+
assert.Equal(t, []string{"Sheet2", "Sheet5", "Sheet4", "Sheet3", "Sheet1"}, sheetList)
1710+
assert.NoError(t, err)
1711+
1712+
err = f.SwapSheets(0, f.SheetCount-1)
1713+
sheetList = f.GetSheetList()
1714+
assert.Equal(t, []string{"Sheet1", "Sheet5", "Sheet4", "Sheet3", "Sheet2"}, sheetList)
1715+
assert.NoError(t, err)
1716+
1717+
// Test SwapSheets with same index
1718+
err = f.SwapSheets(0, 0)
1719+
sheetList = f.GetSheetList()
1720+
assert.Equal(t, []string{"Sheet1", "Sheet5", "Sheet4", "Sheet3", "Sheet2"}, sheetList)
1721+
assert.NoError(t, err)
1722+
1723+
// Swap sheet and test grouping
1724+
err = f.SwapSheets(3, 2)
1725+
sheetList = f.GetSheetList()
1726+
assert.Equal(t, []string{"Sheet1", "Sheet5", "Sheet3", "Sheet4", "Sheet2"}, sheetList)
1727+
assert.NoError(t, err)
1728+
f.SetActiveSheet(2)
1729+
f.GroupSheets([]string{"Sheet2", "Sheet3"})
1730+
for _, sheet := range []string{"Sheet2", "Sheet3"} {
1731+
ws, _ := f.workSheetReader(sheet)
1732+
assert.Equal(t, ws.SheetViews.SheetView[0].TabSelected, true)
1733+
}
1734+
1735+
// Test Swapping the ChartSheet
1736+
err = prepareTestChartSheet(f)
1737+
assert.NoError(t, err)
1738+
sheetList = f.GetSheetList()
1739+
assert.Equal(t, []string{"Sheet1", "Sheet5", "Sheet3", "Sheet4", "Sheet2", "ChartSheet"}, sheetList)
1740+
err = f.SwapSheets(5, 2)
1741+
assert.NoError(t, err)
1742+
sheetList = f.GetSheetList()
1743+
assert.Equal(t, []string{"Sheet1", "Sheet5", "ChartSheet", "Sheet4", "Sheet2", "Sheet3"}, sheetList)
1744+
1745+
// Swap With Error index
1746+
assert.Error(t, f.SwapSheets(-1, 0), ErrSheetIdx)
1747+
assert.Error(t, f.SwapSheets(0, -1), ErrSheetIdx)
1748+
assert.Error(t, f.SwapSheets(f.SheetCount, 0), ErrSheetIdx)
1749+
assert.Error(t, f.SwapSheets(0, f.SheetCount), ErrSheetIdx)
1750+
1751+
// Test Swap with error Workbook
1752+
f.WorkBook = nil
1753+
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
1754+
assert.EqualError(t, f.SwapSheets(0, 1), "XML syntax error on line 1: invalid UTF-8")
1755+
}
1756+
16101757
func prepareTestBook1() (*File, error) {
16111758
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
16121759
if err != nil {
@@ -1712,6 +1859,45 @@ func prepareTestBook5(opts Options) (*File, error) {
17121859
return f, nil
17131860
}
17141861

1862+
func prepareTestChartSheet(f *File) error {
1863+
for idx, row := range [][]interface{}{
1864+
{nil, "Apple", "Orange", "Pear"},
1865+
{"Small", 2, 3, 3},
1866+
{"Normal", 5, 2, 4},
1867+
{"Large", 6, 7, 8},
1868+
} {
1869+
cell, err := CoordinatesToCellName(1, idx+1)
1870+
if err != nil {
1871+
return err
1872+
}
1873+
if err := f.SetSheetRow("Sheet1", cell, &row); err != nil {
1874+
return err
1875+
}
1876+
}
1877+
if err := f.AddChartSheet("ChartSheet", &Chart{
1878+
Type: Col,
1879+
Series: []ChartSeries{
1880+
{
1881+
Name: "Sheet1!$A$2",
1882+
Categories: "Sheet1!$B$1:$D$1",
1883+
Values: "Sheet1!$B$2:$D$2",
1884+
},
1885+
},
1886+
}, &Chart{
1887+
Type: Line,
1888+
Series: []ChartSeries{
1889+
{
1890+
Name: "Sheet1!$A$4",
1891+
Categories: "Sheet1!$B$1:$D$1",
1892+
Values: "Sheet1!$B$4:$D$4",
1893+
},
1894+
},
1895+
}); err != nil {
1896+
return err
1897+
}
1898+
return nil
1899+
}
1900+
17151901
func fillCells(f *File, sheet string, colCount, rowCount int) error {
17161902
for col := 1; col <= colCount; col++ {
17171903
for row := 1; row <= rowCount; row++ {

workbook.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,89 @@ func (f *File) UnprotectWorkbook(password ...string) error {
130130
return err
131131
}
132132

133+
// unselectSheetTab deselects the sheet tab at the specified sheet index
134+
// if it is currently active.
135+
func (f *File) unselectSheetTab(sheetIdx int, wb *xlsxWorkbook) {
136+
activeSheetName := f.GetSheetName(f.GetActiveSheetIndex())
137+
if activeSheetName == wb.Sheets.Sheet[sheetIdx].Name {
138+
ws, err := f.workSheetReader(wb.Sheets.Sheet[sheetIdx].Name)
139+
if err != nil {
140+
return
141+
}
142+
ws.SheetViews.SheetView[0].TabSelected = false
143+
}
144+
}
145+
146+
// SwapSheets swaps the position of two sheets in the workbook
147+
// based on their index positions. If the indices of the sheets
148+
// are the same, the function does nothing. Note that the index
149+
// must be greater than or equal to 0 and less than SheetCount.
150+
// For example:
151+
//
152+
// err := f.SwapSheets(1, 3)
153+
// if err != nil {
154+
// // handle the error
155+
// }
156+
func (f *File) SwapSheets(sheet1Idx, sheet2Idx int) error {
157+
if sheet1Idx == sheet2Idx {
158+
return nil
159+
}
160+
161+
wb, err := f.workbookReader()
162+
if err != nil {
163+
return err
164+
}
165+
if sheet1Idx >= f.SheetCount || sheet2Idx >= f.SheetCount || sheet1Idx < 0 || sheet2Idx < 0 {
166+
return ErrSheetIdx
167+
}
168+
169+
// Unselect the tab of a sheet by index
170+
f.unselectSheetTab(sheet1Idx, wb)
171+
f.unselectSheetTab(sheet2Idx, wb)
172+
173+
tempSheet := wb.Sheets.Sheet[sheet1Idx]
174+
wb.Sheets.Sheet[sheet1Idx] = wb.Sheets.Sheet[sheet2Idx]
175+
wb.Sheets.Sheet[sheet2Idx] = tempSheet
176+
return nil
177+
}
178+
179+
// MoveSheet moves a worksheet to a specified position in the workbook.
180+
// The function takes the index of the source sheet and the target index.
181+
// After moving the worksheet to the target index, other sheets will be
182+
// shifted to the left or right. If the sheet is already at the target position,
183+
// the function will not perform any action. Note that the index must be
184+
// greater than or equal to 0 and less than SheetCount.
185+
// For example:
186+
//
187+
// err := f.MoveSheet(0, 3)
188+
// if err != nil {
189+
// // handle the error
190+
// }
191+
func (f *File) MoveSheet(sourceIdx, targetIdx int) error {
192+
if sourceIdx == targetIdx {
193+
return nil
194+
}
195+
wb, err := f.workbookReader()
196+
if err != nil {
197+
return err
198+
}
199+
200+
if targetIdx >= f.SheetCount || targetIdx < 0 {
201+
return ErrSheetIdx
202+
}
203+
204+
// Unselect the tab of a sheet by index
205+
f.unselectSheetTab(targetIdx, wb)
206+
f.unselectSheetTab(sourceIdx, wb)
207+
208+
sourceSheet := wb.Sheets.Sheet[sourceIdx]
209+
wb.Sheets.Sheet = append(wb.Sheets.Sheet[:sourceIdx], wb.Sheets.Sheet[sourceIdx+1:]...)
210+
sheetsTemp := append([]xlsxSheet{sourceSheet}, wb.Sheets.Sheet[targetIdx:]...)
211+
wb.Sheets.Sheet = append(wb.Sheets.Sheet[:targetIdx], sheetsTemp...)
212+
213+
return nil
214+
}
215+
133216
// setWorkbook update workbook property of the spreadsheet. Maximum 31
134217
// characters are allowed in sheet title.
135218
func (f *File) setWorkbook(name string, sheetID, rid int) {

0 commit comments

Comments
 (0)