Skip to content

Commit

Permalink
feat: XLSX - CSV (#48)
Browse files Browse the repository at this point in the history
* build(gomod): add tealeg/xlsx as dep

* feat(documents): add a csv struct that converts csv to xlsx files

* test(document): add test function to test csv-xlsx

* feat(xlsx): add a xlsx struct able to convert xlsx files to csv ones

* test(documents): add a test function to make sure xlsx - csv works as expected

* feat(documents): add changes to integrate the new kind of documents

* refactor(csv): remove the outdir

* feat(main): add the text kind of document to the main file

* docs(readme): add the csv and xlsx kind of documents to the conversion matrix

* build(gomod): resolve conflicts on go module
  • Loading branch information
danvergara authored Apr 12, 2024
1 parent 617d78c commit 29b489b
Show file tree
Hide file tree
Showing 13 changed files with 577 additions and 17 deletions.
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,12 @@ A modal will pop up with a preview of the converted image.

## Documents X Documents

| | DOCX | PDF |
| ---- | ---- | --- |
| PDF || |
| DOCX | ||
| | DOCX | PDF | XLSX | CSV |
| ---- | ---- | --- | ---- | --- |
| PDF || | | |
| DOCX | || | |
| CSV | | || |
| XLSX | | | ||

## License
The MIT License (MIT). See [LICENSE](LICENSE) file for more details.
12 changes: 10 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,25 @@ require (
github.com/go-chi/chi/v5 v5.0.10
github.com/signintech/gopdf v0.20.0
github.com/stretchr/testify v1.8.4
github.com/tealeg/xlsx/v3 v3.3.6
golang.org/x/image v0.14.0
golang.org/x/text v0.14.0
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/kr/pretty v0.1.0 // indirect
github.com/frankban/quicktest v1.14.6 // indirect
github.com/google/btree v1.0.0 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/peterbourgon/diskv/v3 v3.0.1 // indirect
github.com/phpdave11/gofpdi v1.0.14-0.20211212211723-1f10f9844311 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/fastuuid v1.2.0 // indirect
github.com/rogpeppe/go-internal v1.9.0 // indirect
github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa // indirect
golang.org/x/net v0.18.0 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
37 changes: 30 additions & 7 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,37 +1,60 @@
github.com/chai2010/webp v1.1.1 h1:jTRmEccAJ4MGrhFOrPMpNGIJ/eybIgwKpcACsrTEapk=
github.com/chai2010/webp v1.1.1/go.mod h1:0XVwvZWdjjdxpUEIf7b9g9VkHFnInUSYujwqTLEuldU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gen2brain/go-fitz v1.23.7 h1:HPhzEVzmOINvCKqQgB/DwMzYh4ArIgy3tMwq1eJTcbg=
github.com/gen2brain/go-fitz v1.23.7/go.mod h1:HU04vc+RisUh/kvEd2pB0LAxmK1oyXdN4ftyshUr9rQ=
github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
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/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/peterbourgon/diskv/v3 v3.0.1 h1:x06SQA46+PKIUftmEujdwSEpIx8kR+M9eLYsUxeYveU=
github.com/peterbourgon/diskv/v3 v3.0.1/go.mod h1:kJ5Ny7vLdARGU3WUuy6uzO6T0nb/2gWcT1JiBvRmb5o=
github.com/phpdave11/gofpdi v1.0.14-0.20211212211723-1f10f9844311 h1:zyWXQ6vu27ETMpYsEMAsisQ+GqJ4e1TPvSNfdOPF0no=
github.com/phpdave11/gofpdi v1.0.14-0.20211212211723-1f10f9844311/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.5.0 h1:042Buzk+NhDI+DeSAA62RwJL8VAuZUMQZUjCsRz1Mug=
github.com/pkg/profile v1.5.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa h1:2cO3RojjYl3hVTbEvJVqrMaFmORhL6O06qdW42toftk=
github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa/go.mod h1:Yjr3bdWaVWyME1kha7X0jsz3k2DgXNa1Pj3XGyUAbx8=
github.com/signintech/gopdf v0.20.0 h1:a1rArIMmQCAFzjjCqXPgxynTPkytMccPuGZlUU8Jorw=
github.com/signintech/gopdf v0.20.0/go.mod h1:wrLtZoWaRNrS4hphED0oflFoa6IWkOu6M3nJjm4VbO4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tealeg/xlsx/v3 v3.3.6 h1:b0SPORnNa8BDbFEujljp2IpTDVse3D+Ad5IaMz7KUL8=
github.com/tealeg/xlsx/v3 v3.3.6/go.mod h1:KV4FTFtvGy0TBlOivJLZu/YNZk6e0Qtk7eOSglWksuA=
golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4=
golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
3 changes: 2 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,8 @@ func handleUploadFile(w http.ResponseWriter, r *http.Request) error {
return WithHTTPStatus(err, http.StatusInternalServerError)
}

if fileType == "application" {
switch fileType {
case "application", "text":
targetFileSubType = "zip"
}

Expand Down
4 changes: 4 additions & 0 deletions pkg/files/document_factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ func (d *DocumentFactory) NewFile(f string) (File, error) {
return documents.NewPdf(d.filename), nil
case documents.DOCX, documents.DOCXMIMEType:
return documents.NewDocx(d.filename), nil
case documents.XLSX, documents.XLSXMIMEType:
return documents.NewXlsx(d.filename), nil
case documents.CSV:
return documents.NewCsv(d.filename), nil
default:
return nil, fmt.Errorf("type file file %s not recognized", f)
}
Expand Down
160 changes: 160 additions & 0 deletions pkg/files/documents/csv.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package documents

import (
"archive/zip"
"bytes"
"encoding/csv"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"slices"
"strings"

"github.com/tealeg/xlsx/v3"
)

// Csv struct implements the File and Document interface from the file package.
type Csv struct {
filename string
compatibleFormats map[string][]string
compatibleMIMETypes map[string][]string
}

// NewCsv returns a pointer to Csv.
func NewCsv(filename string) *Csv {
c := Csv{
filename: filename,
compatibleFormats: map[string][]string{
"Document": {
XLSX,
},
},
compatibleMIMETypes: map[string][]string{
"Document": {
XLSX,
},
},
}

return &c
}

// SupportedFormats returns a map witht the compatible formats that CSv is
// compatible to be converted to.
func (c *Csv) SupportedFormats() map[string][]string {
return c.compatibleFormats
}

// SupportedMIMETypes returns a map witht the compatible MIME types that Docx is
// compatible to be converted to.
func (c *Csv) SupportedMIMETypes() map[string][]string {
return c.compatibleMIMETypes
}

func (c *Csv) ConvertTo(fileType, subType string, file io.Reader) (io.Reader, error) {
compatibleFormats, ok := c.SupportedFormats()[fileType]
if !ok {
return nil, fmt.Errorf("file type not supported: %s", fileType)
}

if !slices.Contains(compatibleFormats, subType) {
return nil, fmt.Errorf("sub-type not supported: %s", subType)
}

switch strings.ToLower(fileType) {
case documentType:
switch subType {
case XLSX:
xlsxFilename := fmt.Sprintf(
"%s.xlsx",
strings.TrimSuffix(c.filename, filepath.Ext(c.filename)),
)

xlsxPath := filepath.Join("/tmp", xlsxFilename)

// Parses the file name of the Zip file.
zipFileName := filepath.Join("/tmp", fmt.Sprintf(
"%s.zip",
strings.TrimSuffix(c.filename, filepath.Ext(c.filename)),
))

reader := csv.NewReader(file)
xlsxFile := xlsx.NewFile()
sheet, err := xlsxFile.AddSheet(strings.TrimSuffix(c.filename, filepath.Ext(c.filename)))
if err != nil {
return nil, fmt.Errorf("error creating a xlsx sheet %w", err)
}

for {
fields, err := reader.Read()
if err == io.EOF {
break
}

row := sheet.AddRow()
for _, field := range fields {
cell := row.AddCell()
cell.Value = field
}
}

xlsxFile.Save(xlsxPath)

tmpCsvFile, err := os.Open(xlsxPath)
if err != nil {
return nil, fmt.Errorf(
"error at opening the pdf file: %w",
err,
)
}
defer tmpCsvFile.Close()

// Creates the zip file that will be returned.
archive, err := os.Create(zipFileName)
if err != nil {
return nil, fmt.Errorf(
"error at creating the zip file to store the pdf file: %w",
err,
)
}

// Creates a Zip Writer to add files later on.
zipWriter := zip.NewWriter(archive)

w1, err := zipWriter.Create(xlsxFilename)
if err != nil {
return nil, fmt.Errorf(
"eror at creating a zip file: %w",
err,
)
}

if _, err := io.Copy(w1, tmpCsvFile); err != nil {
return nil, fmt.Errorf(
"error at writing the pdf file content to the zip writer: %w",
err,
)
}

// Closes both zip writer and the zip file after its done with the writing.
zipWriter.Close()
archive.Close()

// Reads the zip file as an slice of bytes.
zipFile, err := os.ReadFile(zipFileName)
if err != nil {
return nil, fmt.Errorf("error reading zip file: %v", err)
}

return bytes.NewReader(zipFile), nil
}
}

return nil, errors.New("not implemented")
}

func (c *Csv) DocumentType() string {
return CSV
}
4 changes: 4 additions & 0 deletions pkg/files/documents/documents.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ const (
DOCX = "docx"
DOCXMIMEType = "vnd.openxmlformats-officedocument.wordprocessingml.document"
PDF = "pdf"
CSV = "csv"
XLSX = "xlsx"
XLSXMIMEType = "vnd.openxmlformats-officedocument.spreadsheetml.sheet"

imageMimeType = "image/"
imageType = "image"

documentMimeType = "application/"
tesxtMimeType = "text/"
documentType = "document"
)
Loading

0 comments on commit 29b489b

Please sign in to comment.