feat: Add sheet management operations to Excel module

- Implement new process handlers for creating, reading, updating, copying, and deleting sheets in Excel files.
- Enhance the Open function to automatically create directories for file paths if they do not exist.
- Update README.md to include detailed documentation and examples for new sheet operations, improving user guidance.
This commit is contained in:
Max 2025-03-29 17:02:18 +08:00
parent 8371a8959f
commit 6e4b7cd59e
6 changed files with 666 additions and 0 deletions

View file

@ -49,6 +49,125 @@ const h2: string = Process("excel.Open", "file.xlsx");
// Process("excel.Close", h);
```
### Sheet Operations
#### Create a new sheet
```typescript
/**
* Creates a new sheet in the workbook
* @param handle - Handle ID from excel.open
* @param name - Name for the new sheet
* @returns number - Index of the new sheet
*/
const idx: number = Process("excel.sheet.create", h, "NewSheet");
```
#### List all sheets
```typescript
/**
* Lists all sheets in the workbook
* @param handle - Handle ID from excel.open
* @returns string[] - Array of sheet names
*/
const sheets: string[] = Process("excel.sheet.list", h);
// Example output: ["Sheet1", "Sheet2", "NewSheet"]
```
#### Read sheet data
```typescript
/**
* Reads all data from a sheet
* @param handle - Handle ID from excel.open
* @param name - Sheet name
* @returns any[][] - Two-dimensional array of cell values
*/
const data: any[][] = Process("excel.sheet.read", h, "Sheet1");
```
#### Update sheet data
```typescript
/**
* Updates data in a sheet. Creates the sheet if it doesn't exist.
* @param handle - Handle ID from excel.open
* @param name - Sheet name
* @param data - Two-dimensional array of values to write
* @returns null
*/
const data = [
["Header1", "Header2", "Header3"],
[1, "Data1", true],
[2, "Data2", false],
];
Process("excel.sheet.update", h, "Sheet1", data);
```
#### Copy a sheet
```typescript
/**
* Copies a sheet with all its content and formatting
* @param handle - Handle ID from excel.open
* @param source - Source sheet name
* @param target - Target sheet name (must not exist)
* @returns null
*/
Process("excel.sheet.copy", h, "Sheet1", "Sheet1Copy");
```
#### Delete a sheet
```typescript
/**
* Deletes a sheet from the workbook
* @param handle - Handle ID from excel.open
* @param name - Sheet name to delete
* @returns null
*/
Process("excel.sheet.delete", h, "Sheet1Copy");
```
### Example: Sheet Operations Workflow
```typescript
// Open Excel file in writable mode
const h: string = Process("excel.Open", "file.xlsx", true);
// Create a new sheet
const idx: number = Process("excel.sheet.create", h, "DataSheet");
// Write some data to the new sheet
const data = [
["Name", "Age", "Active"],
["John", 30, true],
["Jane", 25, false],
];
Process("excel.sheet.update", h, "DataSheet", data);
// Make a backup copy of the sheet
Process("excel.sheet.copy", h, "DataSheet", "DataSheet_Backup");
// List all sheets to verify
const sheets: string[] = Process("excel.sheet.list", h);
console.log("Available sheets:", sheets);
// Read data from the backup sheet
const backupData: any[][] = Process("excel.sheet.read", h, "DataSheet_Backup");
console.log("Backup data:", backupData);
// Delete the backup sheet when no longer needed
Process("excel.sheet.delete", h, "DataSheet_Backup");
// Save changes
Process("excel.Save", h);
// IMPORTANT: Always close the handle when done
Process("excel.Close", h);
```
#### Get all sheets in the workbook
```typescript

View file

@ -39,6 +39,16 @@ func Open(path string, writable bool) (string, error) {
// if the file not exists, create it
if _, err := os.Stat(absPath); os.IsNotExist(err) {
// Auto create dir
dir := filepath.Dir(absPath)
if _, err := os.Stat(dir); os.IsNotExist(err) {
err := os.MkdirAll(dir, 0644)
if err != nil {
return "", err
}
}
create := excelize.NewFile()
err := create.SaveAs(absPath)
if err != nil {

View file

@ -13,6 +13,13 @@ func init() {
"save": processSave,
"sheets": processSheets,
"sheet.create": processCreateSheet,
"sheet.read": processReadSheet,
"sheet.update": processUpdateSheet,
"sheet.delete": processDeleteSheet,
"sheet.copy": processCopySheet,
"sheet.list": processListSheets,
"read.cell": processReadCell,
"read.row": processReadRow,
"read.column": processReadColumn,
@ -623,3 +630,122 @@ func processNextColumn(process *process.Process) interface{} {
return col
}
// processCreateSheet process the excel.sheet.create <handle> <name>
func processCreateSheet(process *process.Process) interface{} {
process.ValidateArgNums(2)
handle := process.ArgsString(0)
name := process.ArgsString(1)
xls, err := Get(handle)
if err != nil {
exception.New("excel.sheet.create %s error: %s", 500, handle, err.Error()).Throw()
}
idx, err := xls.CreateSheet(name)
if err != nil {
exception.New("excel.sheet.create %s:%s error: %s", 500, handle, name, err.Error()).Throw()
}
return idx
}
// processReadSheet process the excel.sheet.read <handle> <name>
func processReadSheet(process *process.Process) interface{} {
process.ValidateArgNums(2)
handle := process.ArgsString(0)
name := process.ArgsString(1)
xls, err := Get(handle)
if err != nil {
exception.New("excel.sheet.read %s error: %s", 500, handle, err.Error()).Throw()
}
data, err := xls.ReadSheet(name)
if err != nil {
exception.New("excel.sheet.read %s:%s error: %s", 500, handle, name, err.Error()).Throw()
}
return data
}
// processUpdateSheet process the excel.sheet.update <handle> <name> <data>
func processUpdateSheet(process *process.Process) interface{} {
process.ValidateArgNums(3)
handle := process.ArgsString(0)
name := process.ArgsString(1)
data := process.Args[2]
xls, err := Get(handle)
if err != nil {
exception.New("excel.sheet.update %s error: %s", 500, handle, err.Error()).Throw()
}
// Convert data to [][]interface{}
var sheetData [][]interface{}
if arr, ok := data.([]interface{}); ok {
for _, row := range arr {
if rowArr, ok := row.([]interface{}); ok {
sheetData = append(sheetData, rowArr)
} else {
sheetData = append(sheetData, []interface{}{row})
}
}
} else {
sheetData = [][]interface{}{{data}}
}
err = xls.UpdateSheet(name, sheetData)
if err != nil {
exception.New("excel.sheet.update %s:%s error: %s", 500, handle, name, err.Error()).Throw()
}
return nil
}
// processDeleteSheet process the excel.sheet.delete <handle> <name>
func processDeleteSheet(process *process.Process) interface{} {
process.ValidateArgNums(2)
handle := process.ArgsString(0)
name := process.ArgsString(1)
xls, err := Get(handle)
if err != nil {
exception.New("excel.sheet.delete %s error: %s", 500, handle, err.Error()).Throw()
}
err = xls.DeleteSheet(name)
if err != nil {
exception.New("excel.sheet.delete %s:%s error: %s", 500, handle, name, err.Error()).Throw()
}
return nil
}
// processCopySheet process the excel.sheet.copy <handle> <source> <target>
func processCopySheet(process *process.Process) interface{} {
process.ValidateArgNums(3)
handle := process.ArgsString(0)
source := process.ArgsString(1)
target := process.ArgsString(2)
xls, err := Get(handle)
if err != nil {
exception.New("excel.sheet.copy %s error: %s", 500, handle, err.Error()).Throw()
}
err = xls.CopySheet(source, target)
if err != nil {
exception.New("excel.sheet.copy %s:%s:%s error: %s", 500, handle, source, target, err.Error()).Throw()
}
return nil
}
// processListSheets process the excel.sheet.list <handle>
func processListSheets(process *process.Process) interface{} {
process.ValidateArgNums(1)
handle := process.ArgsString(0)
xls, err := Get(handle)
if err != nil {
exception.New("excel.sheet.list %s error: %s", 500, handle, err.Error()).Throw()
}
return xls.ListSheets()
}

View file

@ -758,3 +758,161 @@ func TestProcessIterators(t *testing.T) {
_, err = p.Exec()
assert.NoError(t, err)
}
func TestProcessSheetOperations(t *testing.T) {
test.Prepare(t, config.Conf)
defer test.Clean()
files := testFiles(t)
// Create a new test file path
dataRoot := config.Conf.DataRoot
newFile := filepath.Join(filepath.Dir(files["test-01"]), "test-sheet-ops.xlsx")
// Copy test-01.xlsx to new file
content, err := os.ReadFile(filepath.Join(dataRoot, files["test-01"]))
if err != nil {
t.Fatal(err)
}
err = os.WriteFile(filepath.Join(dataRoot, newFile), content, 0644)
if err != nil {
t.Fatal(err)
}
defer os.Remove(filepath.Join(dataRoot, newFile)) // Clean up after test
// Open file in write mode
p, err := process.Of("excel.open", newFile, false)
if err != nil {
t.Fatal(err)
}
handle, err := p.Exec()
if err != nil {
t.Fatal(err)
}
// Test sheet.create
t.Run("CreateSheet", func(t *testing.T) {
p, err := process.Of("excel.sheet.create", handle, "TestSheet1")
assert.NoError(t, err)
idx, err := p.Exec()
assert.NoError(t, err)
assert.Greater(t, idx.(int), 0)
// Try to create a sheet with the same name (should fail)
p, err = process.Of("excel.sheet.create", handle, "TestSheet1")
assert.NoError(t, err)
_, err = p.Exec()
assert.Error(t, err)
})
// Test sheet.list
t.Run("ListSheets", func(t *testing.T) {
p, err := process.Of("excel.sheet.list", handle)
assert.NoError(t, err)
sheets, err := p.Exec()
assert.NoError(t, err)
sheetList := sheets.([]string)
assert.Contains(t, sheetList, "TestSheet1")
})
// Test sheet.update and sheet.read
t.Run("UpdateAndReadSheet", func(t *testing.T) {
testData := [][]interface{}{
{"Header1", "Header2"},
{1, "Data1"},
{2, "Data2"},
}
p, err := process.Of("excel.sheet.update", handle, "TestSheet1", testData)
assert.NoError(t, err)
_, err = p.Exec()
assert.NoError(t, err)
// Read and verify
p, err = process.Of("excel.sheet.read", handle, "TestSheet1")
assert.NoError(t, err)
data, err := p.Exec()
assert.NoError(t, err)
assert.NotNil(t, data)
// Try to read non-existent sheet
p, err = process.Of("excel.sheet.read", handle, "NonExistentSheet")
assert.NoError(t, err)
_, err = p.Exec()
assert.Error(t, err)
})
// Test sheet.copy
t.Run("CopySheet", func(t *testing.T) {
p, err := process.Of("excel.sheet.copy", handle, "TestSheet1", "CopiedSheet")
assert.NoError(t, err)
_, err = p.Exec()
assert.NoError(t, err)
// Verify the copy exists
p, err = process.Of("excel.sheet.list", handle)
assert.NoError(t, err)
sheets, err := p.Exec()
assert.NoError(t, err)
sheetList := sheets.([]string)
assert.Contains(t, sheetList, "CopiedSheet")
// Try to copy to existing sheet name (should fail)
p, err = process.Of("excel.sheet.copy", handle, "TestSheet1", "CopiedSheet")
assert.NoError(t, err)
_, err = p.Exec()
assert.Error(t, err)
})
// Test sheet.delete
t.Run("DeleteSheet", func(t *testing.T) {
p, err := process.Of("excel.sheet.delete", handle, "CopiedSheet")
assert.NoError(t, err)
_, err = p.Exec()
assert.NoError(t, err)
// Verify the sheet is deleted
p, err = process.Of("excel.sheet.list", handle)
assert.NoError(t, err)
sheets, err := p.Exec()
assert.NoError(t, err)
sheetList := sheets.([]string)
assert.NotContains(t, sheetList, "CopiedSheet")
// Try to delete non-existent sheet
p, err = process.Of("excel.sheet.delete", handle, "NonExistentSheet")
assert.NoError(t, err)
_, err = p.Exec()
assert.Error(t, err)
})
// Save and close
p, err = process.Of("excel.save", handle)
if err != nil {
t.Fatal(err)
}
_, err = p.Exec()
assert.NoError(t, err)
p, err = process.Of("excel.close", handle)
if err != nil {
t.Fatal(err)
}
_, err = p.Exec()
assert.NoError(t, err)
}

130
excel/sheet.go Normal file
View file

@ -0,0 +1,130 @@
package excel
import (
"fmt"
"github.com/xuri/excelize/v2"
)
// New creates a new Excel workbook
func New() (*Excel, error) {
f := excelize.NewFile()
return &Excel{
File: f,
id: "",
path: "",
create: 0,
abs: "",
}, nil
}
// CreateSheet creates a new sheet with the given name
// Returns the index of the new sheet and any error encountered
func (excel *Excel) CreateSheet(name string) (int, error) {
// Check if sheet already exists
if idx, _ := excel.GetSheetIndex(name); idx != -1 {
return 0, fmt.Errorf("sheet %s already exists", name)
}
return excel.NewSheet(name)
}
// ReadSheet reads all data from a sheet
// Returns the data as a 2D array of interfaces and any error encountered
func (excel *Excel) ReadSheet(name string) ([][]interface{}, error) {
// Check if sheet exists
if idx, _ := excel.GetSheetIndex(name); idx == -1 {
return nil, fmt.Errorf("sheet %s does not exist", name)
}
rows, err := excel.GetRows(name)
if err != nil {
return nil, err
}
// Convert [][]string to [][]interface{}
result := make([][]interface{}, len(rows))
for i, row := range rows {
result[i] = make([]interface{}, len(row))
for j, cell := range row {
result[i][j] = cell
}
}
return result, nil
}
// UpdateSheet updates an existing sheet with new data
// If the sheet doesn't exist, it will be created
func (excel *Excel) UpdateSheet(name string, data [][]interface{}) error {
// Ensure sheet exists
_, err := excel.SetSheet(name)
if err != nil {
return err
}
// Clear existing content by deleting the sheet
err = excel.DeleteSheet(name)
if err != nil {
return err
}
// Create new sheet with same name
_, err = excel.NewSheet(name)
if err != nil {
return err
}
// Write new data
return excel.WriteAll(name, "A1", data)
}
// DeleteSheet removes a sheet by name
func (excel *Excel) DeleteSheet(name string) error {
// Check if sheet exists
if idx, _ := excel.GetSheetIndex(name); idx == -1 {
return fmt.Errorf("sheet %s does not exist", name)
}
return excel.File.DeleteSheet(name)
}
// ListSheets returns a list of all sheet names in the workbook
func (excel *Excel) ListSheets() []string {
return excel.GetSheetList()
}
// CopySheet copies a sheet to a new name
func (excel *Excel) CopySheet(source, destination string) error {
// Check if source exists
if idx, _ := excel.GetSheetIndex(source); idx == -1 {
return fmt.Errorf("source sheet %s does not exist", source)
}
// Check if destination already exists
if idx, _ := excel.GetSheetIndex(destination); idx != -1 {
return fmt.Errorf("destination sheet %s already exists", destination)
}
// Create new sheet
_, err := excel.NewSheet(destination)
if err != nil {
return err
}
// Copy content
rows, err := excel.GetRows(source)
if err != nil {
return err
}
// Convert [][]string to [][]interface{}
data := make([][]interface{}, len(rows))
for i, row := range rows {
data[i] = make([]interface{}, len(row))
for j, cell := range row {
data[i][j] = cell
}
}
return excel.WriteAll(destination, "A1", data)
}

123
excel/sheet_test.go Normal file
View file

@ -0,0 +1,123 @@
package excel
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestSheetOperations(t *testing.T) {
// Get test files and open the test file
files := testFiles(t)
handler, err := Open(files["test-01"], true) // Open in writable mode
if err != nil {
t.Fatal(err)
}
defer Close(handler)
excel, err := Get(handler)
if err != nil {
t.Fatal(err)
}
// Test CreateSheet
t.Run("CreateSheet", func(t *testing.T) {
// Create a new sheet
idx, err := excel.CreateSheet("TestSheet1")
assert.NoError(t, err)
assert.Greater(t, idx, 0)
// Try to create a sheet with the same name (should fail)
_, err = excel.CreateSheet("TestSheet1")
assert.Error(t, err)
})
// Test ReadSheet
t.Run("ReadSheet", func(t *testing.T) {
// Create test data
testData := [][]interface{}{
{"Header1", "Header2"},
{1, "Data1"},
{2, "Data2"},
}
// Write test data
err := excel.WriteAll("TestSheet1", "A1", testData)
assert.NoError(t, err)
// Read the data back
data, err := excel.ReadSheet("TestSheet1")
assert.NoError(t, err)
assert.Equal(t, len(testData), len(data))
// Try to read non-existent sheet
_, err = excel.ReadSheet("NonExistentSheet")
assert.Error(t, err)
})
// Test UpdateSheet
t.Run("UpdateSheet", func(t *testing.T) {
newData := [][]interface{}{
{"NewHeader1", "NewHeader2"},
{3, "NewData1"},
{4, "NewData2"},
}
// Update existing sheet
err := excel.UpdateSheet("TestSheet1", newData)
assert.NoError(t, err)
// Read back and verify
data, err := excel.ReadSheet("TestSheet1")
assert.NoError(t, err)
assert.Equal(t, len(newData), len(data))
// Update non-existent sheet (should create new)
err = excel.UpdateSheet("NewSheet", newData)
assert.NoError(t, err)
})
// Test ListSheets
t.Run("ListSheets", func(t *testing.T) {
sheets := excel.ListSheets()
assert.Contains(t, sheets, "TestSheet1")
assert.Contains(t, sheets, "NewSheet")
})
// Test CopySheet
t.Run("CopySheet", func(t *testing.T) {
// Copy existing sheet
err := excel.CopySheet("TestSheet1", "CopiedSheet")
assert.NoError(t, err)
// Verify the copy
originalData, err := excel.ReadSheet("TestSheet1")
assert.NoError(t, err)
copiedData, err := excel.ReadSheet("CopiedSheet")
assert.NoError(t, err)
assert.Equal(t, originalData, copiedData)
// Try to copy to existing sheet name (should fail)
err = excel.CopySheet("TestSheet1", "CopiedSheet")
assert.Error(t, err)
// Try to copy non-existent sheet (should fail)
err = excel.CopySheet("NonExistentSheet", "NewSheet2")
assert.Error(t, err)
})
// Test DeleteSheet
t.Run("DeleteSheet", func(t *testing.T) {
// Delete existing sheet
err := excel.DeleteSheet("CopiedSheet")
assert.NoError(t, err)
// Verify sheet is deleted
sheets := excel.ListSheets()
assert.NotContains(t, sheets, "CopiedSheet")
// Try to delete non-existent sheet
err = excel.DeleteSheet("NonExistentSheet")
assert.Error(t, err)
})
}