Skip to content

Commit 01b1854

Browse files
author
mengzhongyuan
committed
refactor: replace StreamWriter.rawData with a TepFile abstraction to support more storage
Change-Id: Ibaca66b80a3ed8793396e1386bf7c0b2db7c21c0 Signed-off-by: mengzhongyuan <[email protected]>
1 parent 249b593 commit 01b1854

File tree

5 files changed

+90
-28
lines changed

5 files changed

+90
-28
lines changed

excelize.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,17 +101,24 @@ type charsetTranscoderFn func(charset string, input io.Reader) (rdr io.Reader, e
101101
//
102102
// CultureInfo specifies the country code for applying built-in language number
103103
// format code these effect by the system's local language settings.
104+
//
105+
// TmpDir specifies the temporary directory for creating temporary files, if the
106+
// value is empty, the system default temporary directory will be used.
107+
//
108+
// StreamingTmpFile specifies the temporary file for streaming writing streaming writer temporary file,
109+
// if the value is nil, the system default temporary file will be used to write streaming data.
104110
type Options struct {
105111
MaxCalcIterations uint
106112
Password string
107113
RawCellValue bool
108114
UnzipSizeLimit int64
109115
UnzipXMLSizeLimit int64
110-
TmpDir string
111116
ShortDatePattern string
112117
LongDatePattern string
113118
LongTimePattern string
114119
CultureInfo CultureName
120+
TmpDir string
121+
StreamingTmpFile *TmpFile
115122
}
116123

117124
// OpenFile take the name of a spreadsheet file and returns a populated

file_test.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,13 @@ func TestWriteTo(t *testing.T) {
6161
f, buf := File{Pkg: sync.Map{}}, bytes.Buffer{}
6262
f.Pkg.Store("s", nil)
6363
f.streams = make(map[string]*StreamWriter)
64-
file, _ := os.Open("123")
65-
f.streams["s"] = &StreamWriter{rawData: bufferedWriter{tmp: file}}
66-
_, err := f.WriteTo(bufio.NewWriter(&buf))
64+
file, err := os.Open("123")
65+
assert.Error(t, err)
66+
67+
rawData := newBufferedWriter(f.options.TmpDir, nil)
68+
rawData.tmp = file
69+
f.streams["s"] = &StreamWriter{rawData: rawData}
70+
_, err = f.WriteTo(bufio.NewWriter(&buf))
6771
assert.Nil(t, err)
6872
}
6973
// Test write with temporary file

lib.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,15 @@ func (f *File) readXML(name string) []byte {
103103
return content.([]byte)
104104
}
105105
if content, ok := f.streams[name]; ok {
106-
return content.rawData.buf.Bytes()
106+
rawDataReader, err := content.rawData.Reader()
107+
if err != nil {
108+
return []byte{}
109+
}
110+
rawDataContent, err := io.ReadAll(rawDataReader)
111+
if err != nil {
112+
return []byte{}
113+
}
114+
return rawDataContent
107115
}
108116
return []byte{}
109117
}

stream.go

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ type StreamWriter struct {
3030
SheetID int
3131
sheetWritten bool
3232
worksheet *xlsxWorksheet
33-
rawData bufferedWriter
33+
rawData TmpFile
3434
rows int
3535
mergeCellsCount int
3636
mergeCells strings.Builder
@@ -119,11 +119,17 @@ func (f *File) NewStreamWriter(sheet string) (*StreamWriter, error) {
119119
if sheetID == -1 {
120120
return nil, ErrSheetNotExist{sheet}
121121
}
122+
123+
rawData := TmpFile(newBufferedWriter(f.options.TmpDir, nil))
124+
if f.options.StreamingTmpFile != nil {
125+
rawData = *f.options.StreamingTmpFile
126+
}
127+
122128
sw := &StreamWriter{
123129
file: f,
124130
Sheet: sheet,
125131
SheetID: sheetID,
126-
rawData: bufferedWriter{tmpDir: f.options.TmpDir},
132+
rawData: rawData,
127133
}
128134
var err error
129135
sw.worksheet, err = f.workSheetReader(sheet)
@@ -138,7 +144,7 @@ func (f *File) NewStreamWriter(sheet string) (*StreamWriter, error) {
138144
f.streams[sheetXMLPath] = sw
139145

140146
_, _ = sw.rawData.WriteString(xml.Header + `<worksheet` + templateNamespaceIDMap)
141-
bulkAppendFields(&sw.rawData, sw.worksheet, 3, 4)
147+
bulkAppendFields(sw.rawData, sw.worksheet, 3, 4)
142148
return sw, err
143149
}
144150

@@ -429,7 +435,7 @@ func (sw *StreamWriter) SetRow(cell string, values []interface{}, opts ...RowOpt
429435
_, _ = sw.rawData.WriteString(`</row>`)
430436
return err
431437
}
432-
writeCell(&sw.rawData, c)
438+
writeCell(sw.rawData, c)
433439
}
434440
_, _ = sw.rawData.WriteString(`</row>`)
435441
return sw.rawData.Sync()
@@ -602,7 +608,7 @@ func setCellIntFunc(c *xlsxC, val interface{}) {
602608
}
603609

604610
// writeCell constructs a cell XML and writes it to the buffer.
605-
func writeCell(buf *bufferedWriter, c xlsxC) {
611+
func writeCell(buf TmpFile, c xlsxC) {
606612
_, _ = buf.WriteString(`<c`)
607613
if c.XMLSpace.Value != "" {
608614
_, _ = buf.WriteString(` xml:`)
@@ -663,7 +669,7 @@ func writeCell(buf *bufferedWriter, c xlsxC) {
663669
// sheetData XML start element to the buffer.
664670
func (sw *StreamWriter) writeSheetData() {
665671
if !sw.sheetWritten {
666-
bulkAppendFields(&sw.rawData, sw.worksheet, 5, 6)
672+
bulkAppendFields(sw.rawData, sw.worksheet, 5, 6)
667673
if sw.worksheet.Cols != nil {
668674
_, _ = sw.rawData.WriteString("<cols>")
669675
for _, col := range sw.worksheet.Cols.Col {
@@ -695,7 +701,7 @@ func (sw *StreamWriter) writeSheetData() {
695701
func (sw *StreamWriter) Flush() error {
696702
sw.writeSheetData()
697703
_, _ = sw.rawData.WriteString(`</sheetData>`)
698-
bulkAppendFields(&sw.rawData, sw.worksheet, 9, 16)
704+
bulkAppendFields(sw.rawData, sw.worksheet, 9, 16)
699705
mergeCells := strings.Builder{}
700706
if sw.mergeCellsCount > 0 {
701707
_, _ = mergeCells.WriteString(`<mergeCells count="`)
@@ -705,9 +711,9 @@ func (sw *StreamWriter) Flush() error {
705711
_, _ = mergeCells.WriteString(`</mergeCells>`)
706712
}
707713
_, _ = sw.rawData.WriteString(mergeCells.String())
708-
bulkAppendFields(&sw.rawData, sw.worksheet, 18, 39)
714+
bulkAppendFields(sw.rawData, sw.worksheet, 18, 39)
709715
_, _ = sw.rawData.WriteString(sw.tableParts)
710-
bulkAppendFields(&sw.rawData, sw.worksheet, 41, 41)
716+
bulkAppendFields(sw.rawData, sw.worksheet, 41, 41)
711717
_, _ = sw.rawData.WriteString(`</worksheet>`)
712718
if err := sw.rawData.Flush(); err != nil {
713719
return err
@@ -733,11 +739,38 @@ func bulkAppendFields(w io.Writer, ws *xlsxWorksheet, from, to int) {
733739
}
734740
}
735741

742+
// TmpFile is an interface for a streaming writer temporary file abstraction, implement it to support
743+
// custom temporary file storage.
744+
type TmpFile interface {
745+
Close() error
746+
Reader() (io.Reader, error)
747+
Sync() error
748+
Write(p []byte) (n int, err error)
749+
WriteString(s string) (n int, err error)
750+
Flush() error
751+
}
752+
753+
// newBufferedWriter create a new bufferedWriter, which will write to a temp
754+
// file if the buffer size exceeds the chunkSize. when chunkSize is nil, the
755+
// default chunkSize which is StreamChunkSize will be used.
756+
func newBufferedWriter(tmpDir string, chunkSize *int) *bufferedWriter {
757+
tarChunkSize := StreamChunkSize
758+
if chunkSize != nil {
759+
tarChunkSize = *chunkSize
760+
}
761+
return &bufferedWriter{
762+
chunkSize: tarChunkSize,
763+
tmpDir: tmpDir,
764+
}
765+
}
766+
736767
// bufferedWriter uses a temp file to store an extended buffer. Writes are
737768
// always made to an in-memory buffer, which will always succeed. The buffer
738769
// is written to the temp file with Sync, which may return an error.
739770
// Therefore, Sync should be periodically called and the error checked.
740771
type bufferedWriter struct {
772+
chunkSize int
773+
741774
tmpDir string
742775
tmp *os.File
743776
buf bytes.Buffer

stream_test.go

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -111,12 +111,14 @@ func TestStreamWriter(t *testing.T) {
111111
assert.NoError(t, streamWriter.rawData.Close())
112112
assert.Error(t, streamWriter.Flush())
113113

114-
streamWriter.rawData.tmp, err = os.CreateTemp(os.TempDir(), "excelize-")
114+
tmpFile, err := os.CreateTemp(os.TempDir(), "excelize-")
115+
assert.NoError(t, err)
116+
streamWriter.rawData.(*bufferedWriter).tmp = tmpFile
117+
115118
assert.NoError(t, err)
116119
_, err = streamWriter.rawData.Reader()
117120
assert.NoError(t, err)
118-
assert.NoError(t, streamWriter.rawData.tmp.Close())
119-
assert.NoError(t, os.Remove(streamWriter.rawData.tmp.Name()))
121+
assert.NoError(t, streamWriter.rawData.Close())
120122

121123
// Test create stream writer with unsupported charset
122124
file = NewFile()
@@ -441,38 +443,46 @@ func TestStreamWriterReader(t *testing.T) {
441443
var (
442444
err error
443445
sw = StreamWriter{
444-
rawData: bufferedWriter{},
446+
rawData: newBufferedWriter("", nil),
445447
}
446448
)
447-
sw.rawData.tmp, err = os.CreateTemp(os.TempDir(), "excelize-")
449+
450+
tmpFile, err := os.CreateTemp(os.TempDir(), "excelize-")
448451
assert.NoError(t, err)
449-
assert.NoError(t, sw.rawData.tmp.Close())
452+
453+
rawData := newBufferedWriter("", nil)
454+
rawData.tmp = tmpFile
455+
sw.rawData = rawData
456+
457+
assert.NoError(t, tmpFile.Close())
450458
// Test reader stat a closed temp file
451459
_, err = sw.rawData.Reader()
452460
assert.Error(t, err)
453461
_, err = sw.getRowValues(1, 1, 1)
454462
assert.Error(t, err)
455-
os.Remove(sw.rawData.tmp.Name())
463+
err = os.Remove(tmpFile.Name())
464+
assert.NoError(t, err)
456465

466+
bw := newBufferedWriter("", nil)
457467
sw = StreamWriter{
458468
file: NewFile(),
459-
rawData: bufferedWriter{},
469+
rawData: bw,
460470
}
461471
// Test getRowValues without expected row
462-
sw.rawData.buf.WriteString("<worksheet><row r=\"1\"><c r=\"B1\"></c></row><worksheet/>")
472+
bw.buf.WriteString("<worksheet><row r=\"1\"><c r=\"B1\"></c></row><worksheet/>")
463473
_, err = sw.getRowValues(1, 1, 1)
464474
assert.NoError(t, err)
465-
sw.rawData.buf.Reset()
475+
bw.buf.Reset()
466476
// Test getRowValues with illegal cell reference
467-
sw.rawData.buf.WriteString("<worksheet><row r=\"1\"><c r=\"A\"></c></row><worksheet/>")
477+
bw.buf.WriteString("<worksheet><row r=\"1\"><c r=\"A\"></c></row><worksheet/>")
468478
_, err = sw.getRowValues(1, 1, 1)
469479
assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), err)
470-
sw.rawData.buf.Reset()
480+
bw.buf.Reset()
471481
// Test getRowValues with invalid c element characters
472-
sw.rawData.buf.WriteString("<worksheet><row r=\"1\"><c></row><worksheet/>")
482+
bw.buf.WriteString("<worksheet><row r=\"1\"><c></row><worksheet/>")
473483
_, err = sw.getRowValues(1, 1, 1)
474484
assert.EqualError(t, err, "XML syntax error on line 1: element <c> closed by </row>")
475-
sw.rawData.buf.Reset()
485+
bw.buf.Reset()
476486
}
477487

478488
func TestStreamWriterGetRowElement(t *testing.T) {

0 commit comments

Comments
 (0)