excelize
excelize copied to clipboard
Function SaveTo to save file to writer
Hi, it would be great to have possibility to pass unlocked file to io.Writer interface.
Use case: Web service downloads excel files from cloud storage and processes it. In case excel file is password protected, service should unlock it with provided password and upload unlocked file back to cloud storage.
Description
Current implementation allows to save unlocked filed to the file on file system via:
func (f *File) SaveAs(name string, opt ...Options) error
New function SaveTo would allow to upload unlocked file without persisting it on file system.
func (f *File) SaveTo(w io.Writer, opt ...Options) (int64, error)
Other option would be to allow reset options in order to save unlocked file using:
func (f *File) Write(w io.Writer) error
func (f *File) WriteTo(w io.Writer) (int64, error)
func (f *File) WriteToBuffer() (*bytes.Buffer, error)
Thanks!
Any update on this from the contributors?
This library has added the WriteTo function since version 1.4.1, which can be used to save the file to the writer. I will add options support for it recently.
Thanks for the quick reply @xuri I see the functions but they don't seem to set the contentType using the file extension as SaveAs
does and that functionality is not exported (setContentTypePartProjectExtensions
). I have a case for a file upload that doesn't need local storage.
Okay, thanks for your feedback. I will add support for it at this week.
I can create a proposal PR. Against v2 or master?
Thank you. Contributions are welcome. Please create PR base on the master branch. Here are some changes already have made for your reference:
diff --git a/crypt_test.go b/crypt_test.go
index f7c465e..95b6f52 100644
--- a/crypt_test.go
+++ b/crypt_test.go
@@ -48,6 +48,8 @@ func TestEncrypt(t *testing.T) {
cell, err = f.GetCellValue("Sheet1", "A1")
assert.NoError(t, err)
assert.Equal(t, "SECRET", cell)
+ // Test remove password by save workbook with options
+ assert.NoError(t, f.Save(Options{Password: ""}))
assert.NoError(t, f.Close())
}
diff --git a/excelize.go b/excelize.go
index f1269fe..bb4bde0 100644
--- a/excelize.go
+++ b/excelize.go
@@ -136,13 +136,13 @@ func newFile() *File {
// OpenReader read data stream from io.Reader and return a populated
// spreadsheet file.
-func OpenReader(r io.Reader, opt ...Options) (*File, error) {
+func OpenReader(r io.Reader, opts ...Options) (*File, error) {
b, err := ioutil.ReadAll(r)
if err != nil {
return nil, err
}
f := newFile()
- f.options = parseOptions(opt...)
+ f.options = parseOptions(opts...)
if f.options.UnzipSizeLimit == 0 {
f.options.UnzipSizeLimit = UnzipSizeLimit
if f.options.UnzipXMLSizeLimit > f.options.UnzipSizeLimit {
diff --git a/excelize_test.go b/excelize_test.go
index 19aba7e..93cd2bf 100644
--- a/excelize_test.go
+++ b/excelize_test.go
@@ -186,18 +186,15 @@ func TestOpenFile(t *testing.T) {
func TestSaveFile(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
- if !assert.NoError(t, err) {
- t.FailNow()
- }
+ assert.NoError(t, err)
assert.EqualError(t, f.SaveAs(filepath.Join("test", "TestSaveFile.xlsb")), ErrWorkbookFileFormat.Error())
for _, ext := range []string{".xlam", ".xlsm", ".xlsx", ".xltm", ".xltx"} {
assert.NoError(t, f.SaveAs(filepath.Join("test", fmt.Sprintf("TestSaveFile%s", ext))))
}
assert.NoError(t, f.Close())
+
f, err = OpenFile(filepath.Join("test", "TestSaveFile.xlsx"))
- if !assert.NoError(t, err) {
- t.FailNow()
- }
+ assert.NoError(t, err)
assert.NoError(t, f.Save())
assert.NoError(t, f.Close())
}
diff --git a/file.go b/file.go
index 065e7c5..c83d17e 100644
--- a/file.go
+++ b/file.go
@@ -55,44 +55,32 @@ func NewFile() *File {
}
// Save provides a function to override the spreadsheet with origin path.
-func (f *File) Save() error {
+func (f *File) Save(opts ...Options) error {
if f.Path == "" {
return ErrSave
}
- if f.options != nil {
- return f.SaveAs(f.Path, *f.options)
+ for i := range opts {
+ f.options = &opts[i]
}
- return f.SaveAs(f.Path)
+ return f.SaveAs(f.Path, *f.options)
}
// SaveAs provides a function to create or update to a spreadsheet at the
// provided path.
-func (f *File) SaveAs(name string, opt ...Options) error {
+func (f *File) SaveAs(name string, opts ...Options) error {
if len(name) > MaxFilePathLength {
return ErrMaxFilePathLength
}
f.Path = name
- contentType, ok := map[string]string{
- ".xlam": ContentTypeAddinMacro,
- ".xlsm": ContentTypeMacro,
- ".xlsx": ContentTypeSheetML,
- ".xltm": ContentTypeTemplateMacro,
- ".xltx": ContentTypeTemplate,
- }[filepath.Ext(f.Path)]
- if !ok {
+ if _, ok := supportedContentType[filepath.Ext(f.Path)]; !ok {
return ErrWorkbookFileFormat
}
- f.setContentTypePartProjectExtensions(contentType)
file, err := os.OpenFile(filepath.Clean(name), os.O_WRONLY|os.O_TRUNC|os.O_CREATE, os.ModePerm)
if err != nil {
return err
}
defer file.Close()
- f.options = nil
- for i := range opt {
- f.options = &opt[i]
- }
- return f.Write(file)
+ return f.Write(file, opts...)
}
// Close closes and cleanup the open temporary file for the spreadsheet.
@@ -113,13 +101,23 @@ func (f *File) Close() error {
}
// Write provides a function to write to an io.Writer.
-func (f *File) Write(w io.Writer) error {
- _, err := f.WriteTo(w)
+func (f *File) Write(w io.Writer, opts ...Options) error {
+ _, err := f.WriteTo(w, opts...)
return err
}
// WriteTo implements io.WriterTo to write the file.
-func (f *File) WriteTo(w io.Writer) (int64, error) {
+func (f *File) WriteTo(w io.Writer, opts ...Options) (int64, error) {
+ for i := range opts {
+ f.options = &opts[i]
+ }
+ if len(f.Path) != 0 {
+ contentType, ok := supportedContentType[filepath.Ext(f.Path)]
+ if !ok {
+ return 0, ErrWorkbookFileFormat
+ }
+ f.setContentTypePartProjectExtensions(contentType)
+ }
if f.options != nil && f.options.Password != "" {
buf, err := f.WriteToBuffer()
if err != nil {
diff --git a/file_test.go b/file_test.go
index 8e65c5d..83a9b78 100644
--- a/file_test.go
+++ b/file_test.go
@@ -71,6 +71,14 @@ func TestWriteTo(t *testing.T) {
_, err := f.WriteTo(bufio.NewWriter(&buf))
assert.EqualError(t, err, "zip: FileHeader.Name too long")
}
+ // Test write with unsupported workbook file format
+ {
+ f, buf := File{Pkg: sync.Map{}}, bytes.Buffer{}
+ f.Pkg.Store("/d", []byte("s"))
+ f.Path = "Book1.xls"
+ _, err := f.WriteTo(bufio.NewWriter(&buf))
+ assert.EqualError(t, err, ErrWorkbookFileFormat.Error())
+ }
}
func TestClose(t *testing.T) {
diff --git a/xmlDrawing.go b/xmlDrawing.go
index 34c9858..fc8dee5 100644
--- a/xmlDrawing.go
+++ b/xmlDrawing.go
@@ -144,6 +144,15 @@ const (
// supportedImageTypes defined supported image types.
var supportedImageTypes = map[string]string{".gif": ".gif", ".jpg": ".jpeg", ".jpeg": ".jpeg", ".png": ".png", ".tif": ".tiff", ".tiff": ".tiff", ".emf": ".emf", ".wmf": ".wmf", ".emz": ".emz", ".wmz": ".wmz"}
+// supportedContentType defined supported file format types.
+var supportedContentType = map[string]string{
+ ".xlam": ContentTypeAddinMacro,
+ ".xlsm": ContentTypeMacro,
+ ".xlsx": ContentTypeSheetML,
+ ".xltm": ContentTypeTemplateMacro,
+ ".xltx": ContentTypeTemplate,
+}
+
// xlsxCNvPr directly maps the cNvPr (Non-Visual Drawing Properties). This
// element specifies non-visual canvas properties. This allows for additional
// information that does not affect the appearance of the picture to be stored.
I have fixed this issue. Now the Save
, Write
and WriteTo
function accept saving options, and here is an example for handle a workbook without a touch file system. Please upgrade to the master branch code, and this patch will be released in the next version.