You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

171 lines
4.8 KiB

package xlsx
import (
"archive/zip"
"bytes"
"fmt"
"io"
"sync"
)
// XlsxFile defines a populated XLSX file struct.
type XlsxFile struct {
Sheets []string
sheetFiles map[string]*zip.File
sharedStrings []string
dateStyles map[int]bool
doneCh chan struct{} // doneCh serves as a signal to abort unfinished operations.
}
// XlsxFileCloser wraps XlsxFile to be able to close an open file
type XlsxFileCloser struct {
zipReadCloser *zip.ReadCloser
XlsxFile
once sync.Once // once performs actions exactly once, e.g. closing a channel.
}
// getFileForName finds and returns a *zip.File by it's display name from within an archive.
// If the file cannot be found, an error is returned.
func getFileForName(files []*zip.File, name string) (*zip.File, error) {
for _, file := range files {
if file.Name == name {
return file, nil
}
}
return nil, fmt.Errorf("file not found: %s", name)
}
// readFile opens and reads the entire contents of a *zip.File into memory.
// If the file cannot be opened, or the data cannot be read, an error is returned.
func readFile(file *zip.File) ([]byte, error) {
rc, err := file.Open()
if err != nil {
return []byte{}, fmt.Errorf("unable to open file: %w", err)
}
defer rc.Close()
buff := bytes.NewBuffer(nil)
_, err = io.Copy(buff, rc)
if err != nil {
return []byte{}, fmt.Errorf("unable to copy bytes: %w", err)
}
return buff.Bytes(), nil
}
// GetSheetFileForSheetName returns the sheet file associated with the sheet name.
// This is useful when you want to further process something out of the sheet, that this
// library does not handle. For example this is useful when trying to read the hyperlinks
// section of a sheet file; getting the sheet file enables you to read the XML directly.
func (xl *XlsxFileCloser) GetSheetFileForSheetName(sheetName string) *zip.File {
sheetFile, _ := xl.sheetFiles[sheetName]
return sheetFile
}
// Close closes the XlsxFile, rendering it unusable for I/O.
func (xl *XlsxFileCloser) Close() error {
if xl == nil {
return nil
}
xl.once.Do(func() { close(xl.doneCh) })
return xl.zipReadCloser.Close()
}
// OpenFile takes the name of an XLSX file and returns a populated XlsxFile struct for it.
// If the file cannot be found, or key parts of the files contents are missing, an error
// is returned.
// Note that the file must be Close()-d when you are finished with it.
func OpenFile(filename string) (*XlsxFileCloser, error) {
zipFile, err := zip.OpenReader(filename)
if err != nil {
return nil, fmt.Errorf("unable to open file reader: %w", err)
}
x := XlsxFile{}
if err := x.init(&zipFile.Reader); err != nil {
zipFile.Close()
return nil, fmt.Errorf("unable to initialise file: %w", err)
}
return &XlsxFileCloser{
XlsxFile: x,
zipReadCloser: zipFile,
}, nil
}
// OpenReaderZip takes the zip ReadCloser of an XLSX file and returns a populated XlsxFileCloser struct for it.
// If the file cannot be found, or key parts of the files contents are missing, an error
// is returned.
// Note that the file must be Close()-d when you are finished with it.
func OpenReaderZip(rc *zip.ReadCloser) (*XlsxFileCloser, error) {
x := XlsxFile{}
if err := x.init(&rc.Reader); err != nil {
rc.Close()
return nil, err
}
return &XlsxFileCloser{
XlsxFile: x,
zipReadCloser: rc,
}, nil
}
// NewReader takes bytes of Xlsx file and returns a populated XlsxFile struct for it.
// If the file cannot be found, or key parts of the files contents are missing, an error
// is returned.
func NewReader(xlsxBytes []byte) (*XlsxFile, error) {
r, err := zip.NewReader(bytes.NewReader(xlsxBytes), int64(len(xlsxBytes)))
if err != nil {
return nil, fmt.Errorf("unable to create new reader: %w", err)
}
x := XlsxFile{}
err = x.init(r)
if err != nil {
return nil, fmt.Errorf("unable to initialise file: %w", err)
}
return &x, nil
}
// NewReaderZip takes zip reader of Xlsx file and returns a populated XlsxFile struct for it.
// If the file cannot be found, or key parts of the files contents are missing, an error
// is returned.
func NewReaderZip(r *zip.Reader) (*XlsxFile, error) {
x := XlsxFile{}
if err := x.init(r); err != nil {
return nil, fmt.Errorf("unable to initialise file: %w", err)
}
return &x, nil
}
func (x *XlsxFile) init(zipReader *zip.Reader) error {
sharedStrings, err := getSharedStrings(zipReader.File)
if err != nil {
return fmt.Errorf("unable to get shared strings: %w", err)
}
sheets, sheetFiles, err := getWorksheets(zipReader.File)
if err != nil {
return fmt.Errorf("unable to get worksheets: %w", err)
}
dateStyles, err := getDateFormatStyles(zipReader.File)
if err != nil {
return fmt.Errorf("unable to get date styles: %w", err)
}
x.sharedStrings = sharedStrings
x.Sheets = sheets
x.sheetFiles = *sheetFiles
x.dateStyles = *dateStyles
x.doneCh = make(chan struct{})
return nil
}