- Implemented `excel.sheet.rows` to read specified rows from a sheet with pagination support. - Added `excel.sheet.dimension` to retrieve the number of rows and columns in a sheet. - Enhanced the Excel processing logic to handle new functionalities and updated related tests. - Updated README with examples for the new features.
314 lines
7.2 KiB
Go
314 lines
7.2 KiB
Go
package excel
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"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
|
|
}
|
|
|
|
// validateSheetName checks if the sheet name contains invalid characters
|
|
func (excel *Excel) validateSheetName(name string) error {
|
|
invalidChars := []string{":", "\\", "/", "?", "*", "[", "]"}
|
|
for _, char := range invalidChars {
|
|
if strings.Contains(name, char) {
|
|
return fmt.Errorf("sheet name cannot contain any of these characters: :/?*[\\]")
|
|
}
|
|
}
|
|
if len(name) == 0 {
|
|
return fmt.Errorf("sheet name cannot be empty")
|
|
}
|
|
if len(name) > 31 {
|
|
return fmt.Errorf("sheet name cannot be longer than 31 characters")
|
|
}
|
|
return 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) {
|
|
// Validate sheet name
|
|
if err := excel.validateSheetName(name); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// GetSheetDimension returns the number of rows and columns in a sheet
|
|
func (excel *Excel) GetSheetDimension(name string) (rows int, cols int, err error) {
|
|
// Check if sheet exists
|
|
if idx, _ := excel.GetSheetIndex(name); idx == -1 {
|
|
return 0, 0, fmt.Errorf("sheet %s does not exist", name)
|
|
}
|
|
rows = 0
|
|
cols = 0
|
|
ri, err := excel.File.Rows(name)
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
defer ri.Close()
|
|
for ri.Next() {
|
|
rows++
|
|
row, err := ri.Columns()
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
if len(row) > cols {
|
|
cols = max(cols, len(row))
|
|
}
|
|
}
|
|
return rows, cols, nil
|
|
|
|
// // Get dimension directly using excelize API
|
|
// dimension, err := excel.File.GetSheetDimension(name)
|
|
// if err != nil {
|
|
// return 0, 0, err
|
|
// }
|
|
|
|
// // If sheet is not set, use row and column count
|
|
// if dimension == "A1" {
|
|
// rows = 0
|
|
// cols = 0
|
|
// ri, err := excel.File.Rows(name)
|
|
// if err != nil {
|
|
// return 0, 0, err
|
|
// }
|
|
// defer ri.Close()
|
|
// for ri.Next() {
|
|
// rows++
|
|
// row, err := ri.Columns()
|
|
// if err != nil {
|
|
// return 0, 0, err
|
|
// }
|
|
// if len(row) > cols {
|
|
// cols = max(cols, len(row))
|
|
// }
|
|
// }
|
|
// return rows, cols, nil
|
|
// }
|
|
|
|
// // If sheet is empty
|
|
// if dimension == "" || dimension == "A1" {
|
|
// return 0, 0, nil
|
|
// }
|
|
|
|
// // For single cell case, add the range suffix
|
|
// if !strings.Contains(dimension, ":") {
|
|
// dimension = fmt.Sprintf("%s:%s", dimension, dimension)
|
|
// }
|
|
|
|
// // Split dimension into start and end coordinates
|
|
// parts := strings.Split(dimension, ":")
|
|
// if len(parts) != 2 {
|
|
// return 0, 0, fmt.Errorf("invalid dimension format: %s", dimension)
|
|
// }
|
|
|
|
// // Convert end coordinate to row and column numbers
|
|
// endCol, endRow, err := excelize.CellNameToCoordinates(parts[1])
|
|
// if err != nil {
|
|
// return 0, 0, err
|
|
// }
|
|
|
|
// // Convert start coordinate to row and column numbers
|
|
// startCol, startRow, err := excelize.CellNameToCoordinates(parts[0])
|
|
// if err != nil {
|
|
// return 0, 0, err
|
|
// }
|
|
|
|
// // Calculate actual dimensions
|
|
// rows = endRow - startRow + 1
|
|
// cols = endCol - startCol + 1
|
|
|
|
// return rows, cols, nil
|
|
}
|
|
|
|
// ReadSheetRows reads all data from a sheet by rows
|
|
func (excel *Excel) ReadSheetRows(name string, start int, size int) ([][]string, error) {
|
|
// Validate parameters
|
|
if start < 0 {
|
|
return nil, fmt.Errorf("start position cannot be negative")
|
|
}
|
|
if size < 0 {
|
|
return nil, fmt.Errorf("size cannot be negative")
|
|
}
|
|
|
|
// Check if sheet exists
|
|
if idx, _ := excel.GetSheetIndex(name); idx == -1 {
|
|
return nil, fmt.Errorf("sheet %s does not exist", name)
|
|
}
|
|
|
|
// If size is 0, return empty slice
|
|
if size == 0 {
|
|
return [][]string{}, nil
|
|
}
|
|
|
|
// Get rows iterator
|
|
rows, err := excel.File.Rows(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
// Skip to start position
|
|
currentRow := -1
|
|
for rows.Next() {
|
|
currentRow++
|
|
if currentRow >= start {
|
|
break
|
|
}
|
|
}
|
|
|
|
// Read requested number of rows
|
|
result := make([][]string, 0, size)
|
|
if currentRow == start {
|
|
row, err := rows.Columns()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result = append(result, row)
|
|
}
|
|
|
|
for i := 1; i < size && rows.Next(); i++ {
|
|
row, err := rows.Columns()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result = append(result, row)
|
|
}
|
|
|
|
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 {
|
|
// Validate sheet name
|
|
if err := excel.validateSheetName(name); err != nil {
|
|
return err
|
|
}
|
|
|
|
// 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()
|
|
}
|
|
|
|
// SheetExists checks if a sheet exists in the workbook
|
|
func (excel *Excel) SheetExists(name string) bool {
|
|
idx, _ := excel.GetSheetIndex(name)
|
|
return idx != -1
|
|
}
|
|
|
|
// CopySheet copies a sheet to a new name
|
|
func (excel *Excel) CopySheet(source, destination string) error {
|
|
// Validate destination sheet name
|
|
if err := excel.validateSheetName(destination); err != nil {
|
|
return err
|
|
}
|
|
|
|
// 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)
|
|
}
|