-
Notifications
You must be signed in to change notification settings - Fork 21
/
file.go
171 lines (142 loc) · 4.78 KB
/
file.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
package xlsxreader
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
}