diff --git a/docs/docs.go b/docs/docs.go index 10c7082..8ba4cf4 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -52,11 +52,11 @@ var doc = `{ } }, { - "description": "Sheets to extract", - "name": "sheets", + "description": "Reader optional options", + "name": "options", "in": "body", "schema": { - "type": "string" + "$ref": "#/definitions/models.ReaderOption" } } ], @@ -100,11 +100,11 @@ var doc = `{ } }, { - "description": "Sheets to extract", - "name": "sheets", + "description": "Reader optional options", + "name": "options", "in": "body", "schema": { - "type": "string" + "$ref": "#/definitions/models.ReaderOption" } } ], @@ -383,6 +383,17 @@ var doc = `{ } } }, + "models.Option": { + "type": "object", + "properties": { + "coordinates": { + "type": "string" + }, + "sheetname": { + "type": "string" + } + } + }, "models.PlotArea": { "type": "object", "properties": { @@ -417,6 +428,17 @@ var doc = `{ } } }, + "models.ReaderOption": { + "type": "object", + "properties": { + "options": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Option" + } + } + } + }, "models.Series": { "type": "object", "properties": { diff --git a/docs/swagger.json b/docs/swagger.json index 1555398..7dd257b 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -35,11 +35,11 @@ } }, { - "description": "Sheets to extract", - "name": "sheets", + "description": "Reader optional options", + "name": "options", "in": "body", "schema": { - "type": "string" + "$ref": "#/definitions/models.ReaderOption" } } ], @@ -83,11 +83,11 @@ } }, { - "description": "Sheets to extract", - "name": "sheets", + "description": "Reader optional options", + "name": "options", "in": "body", "schema": { - "type": "string" + "$ref": "#/definitions/models.ReaderOption" } } ], @@ -366,6 +366,17 @@ } } }, + "models.Option": { + "type": "object", + "properties": { + "coordinates": { + "type": "string" + }, + "sheetname": { + "type": "string" + } + } + }, "models.PlotArea": { "type": "object", "properties": { @@ -400,6 +411,17 @@ } } }, + "models.ReaderOption": { + "type": "object", + "properties": { + "options": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Option" + } + } + } + }, "models.Series": { "type": "object", "properties": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 56d5573..f8c0544 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -143,6 +143,13 @@ definitions: show_legend_key: type: boolean type: object + models.Option: + properties: + coordinates: + type: string + sheetname: + type: string + type: object models.PlotArea: properties: show_bubble_size: @@ -165,6 +172,13 @@ definitions: locked: type: boolean type: object + models.ReaderOption: + properties: + options: + items: + $ref: '#/definitions/models.Option' + type: array + type: object models.Series: properties: categories: @@ -235,11 +249,11 @@ paths: required: true schema: type: string - - description: Sheets to extract + - description: Reader optional options in: body - name: sheets + name: options schema: - type: string + $ref: '#/definitions/models.ReaderOption' produces: - application/json responses: @@ -266,11 +280,11 @@ paths: required: true schema: type: string - - description: Sheets to extract + - description: Reader optional options in: body - name: sheets + name: options schema: - type: string + $ref: '#/definitions/models.ReaderOption' produces: - application/json responses: diff --git a/go.sum b/go.sum index 5ff93e2..5c9feca 100644 --- a/go.sum +++ b/go.sum @@ -89,6 +89,7 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= diff --git a/handlers/reader.go b/handlers/reader.go index acf38d9..d4dc8de 100644 --- a/handlers/reader.go +++ b/handlers/reader.go @@ -3,8 +3,8 @@ package handlers import ( "encoding/json" "net/http" - "strings" + "github.com/Los-Crackitos/Excelante/models" "github.com/Los-Crackitos/Excelante/services" ) @@ -15,21 +15,20 @@ import ( // @Accept mpfd // @Produce json // @Param file body string true "The Excel file to convert" -// @Param sheets body string false "Sheets to extract" +// @Param options body models.ReaderOption false "Reader optional options" // @Success 200 {object} services.Output // @Failure 400 {string} string // @Router /read/lines [post] func ReadExcelFileByLine(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") file, _, _ := r.FormFile("file") r.ParseForm() - sheets := r.Form.Get("sheets") + options := r.Form.Get("options") - var sheetsToExtract []string - if sheets != "" { - sheetsToExtract = strings.Split(sheets, ",") - } + readerOptions := models.ReaderOption{} + json.Unmarshal([]byte(options), &readerOptions) - output, err := services.ReadLines(file, sheetsToExtract) + output, err := services.ReadLines(file, readerOptions) if err != nil { http.Error(w, "An error occurred during file reading", http.StatusBadRequest) @@ -37,7 +36,6 @@ func ReadExcelFileByLine(w http.ResponseWriter, r *http.Request) { return } - w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(output) } @@ -49,21 +47,20 @@ func ReadExcelFileByLine(w http.ResponseWriter, r *http.Request) { // @Accept mpfd // @Produce json // @Param file body string true "The Excel file to convert" -// @Param sheets body string false "Sheets to extract" +// @Param options body models.ReaderOption false "Reader optional options" // @Success 200 {object} services.Output // @Failure 400 {string} string // @Router /read/columns [post] func ReadExcelFileByColumn(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") file, _, _ := r.FormFile("file") r.ParseForm() - sheets := r.Form.Get("sheets") + options := r.Form.Get("options") - var sheetsToExtract []string - if sheets != "" { - sheetsToExtract = strings.Split(sheets, ",") - } + readerOptions := models.ReaderOption{} + json.Unmarshal([]byte(options), &readerOptions) - output, err := services.ReadColumns(file, sheetsToExtract) + output, err := services.ReadColumns(file, readerOptions) if err != nil { http.Error(w, "An error occurred during file reading", http.StatusBadRequest) @@ -71,7 +68,6 @@ func ReadExcelFileByColumn(w http.ResponseWriter, r *http.Request) { return } - w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(output) } diff --git a/models/reader.go b/models/reader.go new file mode 100644 index 0000000..c2073ee --- /dev/null +++ b/models/reader.go @@ -0,0 +1,12 @@ +package models + +// ReaderOption contains reader optional options +type ReaderOption struct { + Options []Option `json:"options"` +} + +// Option represent a reader option object +type Option struct { + SheetName string `json:"sheet_name"` + StartingCoordinates string `json:"starting_coordinates,omitempty"` +} diff --git a/services/reader.go b/services/reader.go index 388acae..e7626b3 100644 --- a/services/reader.go +++ b/services/reader.go @@ -4,6 +4,8 @@ import ( "mime/multipart" "strings" + "github.com/Los-Crackitos/Excelante/models" + "github.com/360EntSecGroup-Skylar/excelize/v2" ) @@ -12,7 +14,7 @@ type Output map[string]map[int]interface{} // ReadLines read all lines of a given Excel file // Returns all values of the file using the Output type or an error -func ReadLines(file multipart.File, sheetsToExtract []string) (Output, error) { +func ReadLines(file multipart.File, readerOptions models.ReaderOption) (Output, error) { output := make(Output) f, err := excelize.OpenReader(file) @@ -20,9 +22,16 @@ func ReadLines(file multipart.File, sheetsToExtract []string) (Output, error) { return nil, err } + initialColIndex := 1 + initialRowIndex := 1 + sheetFound := false + for _, sheetName := range f.GetSheetMap() { - if len(sheetsToExtract) > 0 && !sheetFinder(sheetName, sheetsToExtract) { - continue + if len(readerOptions.Options) > 0 { + sheetFound, initialColIndex, initialRowIndex = sheetFinder(sheetName, readerOptions) + if !sheetFound { + continue + } } output[sheetName] = make(map[int]interface{}) @@ -40,9 +49,16 @@ func ReadLines(file multipart.File, sheetsToExtract []string) (Output, error) { return nil, err } + if currentRowIndex < initialRowIndex { + currentRowIndex++ + continue + } + rowValue := make([]interface{}, 0) - for _, cellValue := range row { + for i := (initialColIndex - 1); i < len(row); i++ { + cellValue := row[i] + if cellValue == "" { cellValue = "N/A" } @@ -60,7 +76,7 @@ func ReadLines(file multipart.File, sheetsToExtract []string) (Output, error) { // ReadColumns read all columns of a given Excel file // Returns all values of the file using the Output type or an error -func ReadColumns(file multipart.File, sheetsToExtract []string) (Output, error) { +func ReadColumns(file multipart.File, readerOptions models.ReaderOption) (Output, error) { output := make(Output) f, err := excelize.OpenReader(file) @@ -68,9 +84,16 @@ func ReadColumns(file multipart.File, sheetsToExtract []string) (Output, error) return nil, err } + initialColIndex := 1 + initialRowIndex := 1 + sheetFound := false + for _, sheetName := range f.GetSheetMap() { - if len(sheetsToExtract) > 0 && !sheetFinder(sheetName, sheetsToExtract) { - continue + if len(readerOptions.Options) > 0 { + sheetFound, initialColIndex, initialRowIndex = sheetFinder(sheetName, readerOptions) + if !sheetFound { + continue + } } output[sheetName] = make(map[int]interface{}) @@ -88,9 +111,16 @@ func ReadColumns(file multipart.File, sheetsToExtract []string) (Output, error) return nil, err } + if currentColIndex < initialColIndex { + currentColIndex++ + continue + } + rowValue := make([]interface{}, 0) - for _, cellValue := range col { + for i := (initialRowIndex - 1); i < len(col); i++ { + cellValue := col[i] + if cellValue == "" { cellValue = "N/A" } @@ -106,11 +136,15 @@ func ReadColumns(file multipart.File, sheetsToExtract []string) (Output, error) return output, nil } -func sheetFinder(sheetName string, sheetsToExtract []string) bool { - for _, v := range sheetsToExtract { - if v == sheetName { - return true +func sheetFinder(sheetName string, readerOptions models.ReaderOption) (bool, int, int) { + for _, option := range readerOptions.Options { + if option.SheetName == sheetName { + if option.StartingCoordinates != "" { + initialColIndex, initialRowIndex, _ := excelize.CellNameToCoordinates(option.StartingCoordinates) + return true, initialColIndex, initialRowIndex + } + return true, 1, 1 } } - return false + return false, 1, 1 } diff --git a/services/reader_test.go b/services/reader_test.go index bb72608..8aa187c 100644 --- a/services/reader_test.go +++ b/services/reader_test.go @@ -4,18 +4,20 @@ import ( "os" "testing" + "github.com/Los-Crackitos/Excelante/models" + "github.com/stretchr/testify/assert" ) func TestReadLines(t *testing.T) { protectedFile, _ := os.Open("../test/protected_file.xlsx") - protectedFileOutput, protectedFileErr := ReadLines(protectedFile, nil) + protectedFileOutput, protectedFileErr := ReadLines(protectedFile, models.ReaderOption{}) assert.Nil(t, protectedFileOutput) assert.Error(t, protectedFileErr) normalFile, _ := os.Open("../test/input.xlsx") - normalFileOutput, normalFileErr := ReadLines(normalFile, nil) + normalFileOutput, normalFileErr := ReadLines(normalFile, models.ReaderOption{}) assert.NoError(t, normalFileErr) @@ -29,7 +31,39 @@ func TestReadLines(t *testing.T) { assert.Contains(t, normalFileOutput["Feuil1"][4], "N/A", "Fourth Row should contains \"N/A\"") normalFile, _ = os.Open("../test/input.xlsx") - normalFileOutput, normalFileErr = ReadLines(normalFile, []string{"Feuil2"}) + readerOption := models.ReaderOption{ + Options: []models.Option{ + models.Option{ + SheetName: "Feuil2", + StartingCoordinates: "A2", + }, + }, + } + normalFileOutput, normalFileErr = ReadLines(normalFile, readerOption) + + assert.NoError(t, normalFileErr) + + firstRow = normalFileOutput["Feuil2"][1] + assert.Nil(t, firstRow) + + firstRow = normalFileOutput["Feuil2"][2] + + assert.Contains(t, firstRow, "feuil2 A2", "First Row should contains \"feuil2 A2\"") + assert.Contains(t, firstRow, "feuil2 B2", "First Row should contains \"feuil2 B2\"") + assert.NotContains(t, firstRow, "Cell A2", "First Row should not contains \"Cell A2\"") + assert.NotContains(t, firstRow, "Cell B3", "First Row should not contains \"Cell B3\"") + + assert.Contains(t, normalFileOutput["Feuil2"][3], "N/A", "Third Row should contains \"N/A\"") + + normalFile, _ = os.Open("../test/input.xlsx") + readerOption = models.ReaderOption{ + Options: []models.Option{ + models.Option{ + SheetName: "Feuil2", + }, + }, + } + normalFileOutput, normalFileErr = ReadLines(normalFile, readerOption) assert.NoError(t, normalFileErr) @@ -41,18 +75,17 @@ func TestReadLines(t *testing.T) { assert.NotContains(t, firstRow, "Cell B3", "First Row should not contains \"Cell B3\"") assert.Contains(t, normalFileOutput["Feuil2"][3], "N/A", "Third Row should contains \"N/A\"") - } func TestReadColumns(t *testing.T) { protectedFile, _ := os.Open("../test/protected_file.xlsx") - protectedFileOutput, protectedFileErr := ReadColumns(protectedFile, nil) + protectedFileOutput, protectedFileErr := ReadColumns(protectedFile, models.ReaderOption{}) assert.Nil(t, protectedFileOutput) assert.Error(t, protectedFileErr) normalFile, _ := os.Open("../test/input.xlsx") - normalFileOutput, normalFileErr := ReadColumns(normalFile, nil) + normalFileOutput, normalFileErr := ReadColumns(normalFile, models.ReaderOption{}) assert.NoError(t, normalFileErr) @@ -66,7 +99,41 @@ func TestReadColumns(t *testing.T) { assert.Contains(t, firstCol, "N/A", "Fourth Col should contains \"N/A\"") normalFile, _ = os.Open("../test/input.xlsx") - normalFileOutput, normalFileErr = ReadColumns(normalFile, []string{"Feuil2"}) + readerOption := models.ReaderOption{ + Options: []models.Option{ + models.Option{ + SheetName: "Feuil2", + StartingCoordinates: "B2", + }, + }, + } + + normalFileOutput, normalFileErr = ReadColumns(normalFile, readerOption) + + assert.NoError(t, normalFileErr) + + firstCol = normalFileOutput["Feuil2"][1] + assert.Nil(t, firstCol) + + firstCol = normalFileOutput["Feuil2"][2] + + assert.Contains(t, firstCol, "feuil2 B2", "First Col should contains \"feuil2 B2\"") + assert.Contains(t, firstCol, "feuil2 B3", "First Col should contains \"feuil2 B3\"") + assert.NotContains(t, firstCol, "Cell B1", "First Col should not contains \"Cell A2\"") + assert.NotContains(t, firstCol, "Cell B2", "First Col should not contains \"Cell B3\"") + + assert.Contains(t, firstCol, "N/A", "Third Col should contains \"N/A\"") + + normalFile, _ = os.Open("../test/input.xlsx") + readerOption = models.ReaderOption{ + Options: []models.Option{ + models.Option{ + SheetName: "Feuil2", + }, + }, + } + + normalFileOutput, normalFileErr = ReadColumns(normalFile, readerOption) assert.NoError(t, normalFileErr) @@ -78,5 +145,4 @@ func TestReadColumns(t *testing.T) { assert.NotContains(t, firstCol, "Cell B2", "First Col should not contains \"Cell B3\"") assert.Contains(t, firstCol, "N/A", "Third Col should contains \"N/A\"") - } diff --git a/test/input.xlsx b/test/input.xlsx index 12c62ba..a0ea85f 100644 Binary files a/test/input.xlsx and b/test/input.xlsx differ diff --git a/test/~$input.xlsx b/test/~$input.xlsx new file mode 100644 index 0000000..5a93205 Binary files /dev/null and b/test/~$input.xlsx differ