diff --git a/go.mod b/go.mod index 733011fe2c..c8448f635f 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/xuri/excelize/v2 go 1.16 require ( + github.com/google/go-cmp v0.5.9 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 github.com/richardlehane/mscfb v1.0.4 github.com/richardlehane/msoleps v1.0.3 // indirect diff --git a/go.sum b/go.sum index d82097a3b1..d13d56c1f2 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= diff --git a/table.go b/table.go index 6aa1552edd..2058940009 100644 --- a/table.go +++ b/table.go @@ -210,6 +210,69 @@ func (f *File) DeleteTable(name string) error { return newNoExistTableError(name) } +// GetTableData provides the method to return the values of a table given its range +// and worksheet name. +func (f *File) GetTableData(sheet string, tableRange string) ([][]string, error) { + coordinates, err := rangeRefToCoordinates(tableRange) + if err != nil { + return nil, fmt.Errorf("failed converting range '%v' to coordinates: %w", tableRange, err) + } + _ = sortCoordinates(coordinates) + + var data [][]string + for row := coordinates[1]; row <= coordinates[3]; row++ { + var rowValues []string + for col := coordinates[0]; col <= coordinates[2]; col++ { + cell, _ := CoordinatesToCellName(col, row) + cellValue, err := f.GetCellValue(sheet, cell) + if err != nil { + return nil, fmt.Errorf("failed getting cell value of '%s': %w", cell, err) + } + rowValues = append(rowValues, cellValue) + } + data = append(data, rowValues) + } + + return data, nil +} + +// GetTableColumns provides the method to return the values of a table as a map with the headers as the keys, +// given its range and worksheet name. +func (f *File) GetTableColumns(sheet string, table Table) (map[string][]string, error) { + coordinates, err := rangeRefToCoordinates(table.Range) + if err != nil { + return nil, fmt.Errorf("failed converting range '%v' to coordinates: %w", table.Range, err) + } + _ = sortCoordinates(coordinates) + + data := make(map[string][]string, coordinates[2]-coordinates[0]+1) + for col := coordinates[0]; col <= coordinates[2]; col++ { + header := "Column" + strconv.Itoa(col-coordinates[0]+1) + firstRow := coordinates[1] + if table.ShowHeaderRow != nil && *table.ShowHeaderRow { + cell, _ := CoordinatesToCellName(col, coordinates[1]) + header, err = f.GetCellValue(sheet, cell) + if err != nil { + return nil, fmt.Errorf("failed getting header from '%s': %w", cell, err) + } + firstRow++ + } + + var rowValues []string + for row := firstRow; row <= coordinates[3]; row++ { + cell, _ := CoordinatesToCellName(col, row) + cellValue, err := f.GetCellValue(sheet, cell) + if err != nil { + return nil, fmt.Errorf("failed getting cell value of '%s': %w", cell, err) + } + rowValues = append(rowValues, cellValue) + } + data[header] = rowValues + } + + return data, nil +} + // countTables provides a function to get table files count storage in the // folder xl/tables. func (f *File) countTables() int { diff --git a/table_test.go b/table_test.go index da4426586b..1f11172190 100644 --- a/table_test.go +++ b/table_test.go @@ -6,6 +6,8 @@ import ( "strings" "testing" + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/assert" ) @@ -99,6 +101,69 @@ func TestGetTables(t *testing.T) { assert.NoError(t, err) } +func TestTableValuesExtraction(t *testing.T) { + sheetName := "Sheet1" + f := NewFile() + + // Set headers + assert.NoError(t, f.SetCellValue(sheetName, "B3", "ID")) + assert.NoError(t, f.SetCellValue(sheetName, "C3", "Type")) + assert.NoError(t, f.SetCellValue(sheetName, "D3", "Name")) + + // Set values + assert.NoError(t, f.SetCellValue(sheetName, "B4", 4)) + assert.NoError(t, f.SetCellValue(sheetName, "C4", "Fire")) + assert.NoError(t, f.SetCellValue(sheetName, "D4", "Charmander")) + assert.NoError(t, f.SetCellValue(sheetName, "B5", 7)) + assert.NoError(t, f.SetCellValue(sheetName, "C5", "Water")) + assert.NoError(t, f.SetCellValue(sheetName, "D5", "Squirtle")) + assert.NoError(t, f.SetCellValue(sheetName, "B6", 25)) + assert.NoError(t, f.SetCellValue(sheetName, "C6", "Electric")) + assert.NoError(t, f.SetCellValue(sheetName, "D6", "Pikachu")) + + // Add the table + pokemonTable := &Table{ + Range: "B3:D6", + Name: "PokemonsTable", + ShowHeaderRow: boolPtr(true), + } + assert.NoError(t, f.AddTable(sheetName, pokemonTable)) + + // Test GetTableData + data, err := f.GetTableData(sheetName, pokemonTable.Range) + assert.NoError(t, err) + expectedData := [][]string{ + {"ID", "Type", "Name"}, + {"4", "Fire", "Charmander"}, + {"7", "Water", "Squirtle"}, + {"25", "Electric", "Pikachu"}, + } + assert.True(t, cmp.Equal(data, expectedData), cmp.Diff(data, expectedData)) + + // Test GetTableColumns with headers + pokemonHeadersColumns, err := f.GetTableColumns(sheetName, *pokemonTable) + assert.NoError(t, err) + expectedHeadersData := map[string][]string{ + "ID": {"4", "7", "25"}, + "Type": {"Fire", "Water", "Electric"}, + "Name": {"Charmander", "Squirtle", "Pikachu"}, + } + assert.True(t, cmp.Equal(pokemonHeadersColumns, expectedHeadersData), + cmp.Diff(pokemonHeadersColumns, expectedHeadersData)) + + // Test GetTableColumns without headers + pokemonTable.ShowHeaderRow = boolPtr(false) + pokemonColumns, err := f.GetTableColumns(sheetName, *pokemonTable) + assert.NoError(t, err) + expectedColumnsData := map[string][]string{ + "Column1": {"ID", "4", "7", "25"}, + "Column2": {"Type", "Fire", "Water", "Electric"}, + "Column3": {"Name", "Charmander", "Squirtle", "Pikachu"}, + } + assert.True(t, cmp.Equal(pokemonColumns, expectedColumnsData), + cmp.Diff(pokemonColumns, expectedColumnsData)) +} + func TestDeleteTable(t *testing.T) { f := NewFile() assert.NoError(t, f.AddTable("Sheet1", &Table{Range: "A1:B4", Name: "Table1"}))