stream.go 21.2 KB
Newer Older
xurime's avatar
xurime 已提交
1
// Copyright 2016 - 2023 The excelize Authors. All rights reserved. Use of
2 3 4
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
5 6 7 8 9
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
xurime's avatar
xurime 已提交
10
// data. This library needs Go version 1.16 or later.
11 12 13 14 15 16 17

package excelize

import (
	"bytes"
	"encoding/xml"
	"fmt"
C
Cameron Howey 已提交
18
	"io"
19 20
	"os"
	"reflect"
C
Cameron Howey 已提交
21 22 23
	"strconv"
	"strings"
	"time"
24 25 26 27
)

// StreamWriter defined the type of stream writer.
type StreamWriter struct {
28
	file            *File
29 30
	Sheet           string
	SheetID         int
31
	sheetWritten    bool
32
	cols            strings.Builder
33 34
	worksheet       *xlsxWorksheet
	rawData         bufferedWriter
35
	rows            int
36
	mergeCellsCount int
37
	mergeCells      strings.Builder
38
	tableParts      string
39 40
}

41 42 43 44 45 46 47 48 49 50
// NewStreamWriter returns stream writer struct by given worksheet name used for
// writing data on a new existing empty worksheet with large amounts of data.
// Note that after writing data with the stream writer for the worksheet, you
// must call the 'Flush' method to end the streaming writing process, ensure
// that the order of row numbers is ascending when set rows, and the normal
// mode functions and stream mode functions can not be work mixed to writing
// data on the worksheets. The stream writer will try to use temporary files on
// disk to reduce the memory usage when in-memory chunks data over 16MB, and
// you can't get cell value at this time. For example, set data for worksheet
// of size 102400 rows x 50 columns with numbers and style:
51
//
52
//	f := excelize.NewFile()
53
//	defer func() {
54
//	    if err := f.Close(); err != nil {
55 56 57
//	        fmt.Println(err)
//	    }
//	}()
58
//	sw, err := f.NewStreamWriter("Sheet1")
59 60
//	if err != nil {
//	    fmt.Println(err)
xurime's avatar
xurime 已提交
61
//	    return
62
//	}
63
//	styleID, err := f.NewStyle(&excelize.Style{Font: &excelize.Font{Color: "777777"}})
64 65
//	if err != nil {
//	    fmt.Println(err)
xurime's avatar
xurime 已提交
66
//	    return
67
//	}
68
//	if err := sw.SetRow("A1",
69 70 71 72 73 74 75
//	    []interface{}{
//	        excelize.Cell{StyleID: styleID, Value: "Data"},
//	        []excelize.RichTextRun{
//	            {Text: "Rich ", Font: &excelize.Font{Color: "2354e8"}},
//	            {Text: "Text", Font: &excelize.Font{Color: "e83723"}},
//	        },
//	    },
76 77
//	    excelize.RowOpts{Height: 45, Hidden: false}); err != nil {
//	    fmt.Println(err)
xurime's avatar
xurime 已提交
78
//	    return
79 80 81 82 83 84
//	}
//	for rowID := 2; rowID <= 102400; rowID++ {
//	    row := make([]interface{}, 50)
//	    for colID := 0; colID < 50; colID++ {
//	        row[colID] = rand.Intn(640000)
//	    }
85 86
//	    cell, err := excelize.CoordinatesToCellName(1, rowID)
//	    if err != nil {
87
//	        fmt.Println(err)
88 89 90 91 92
//	        break
//	    }
//	    if err := sw.SetRow(cell, row); err != nil {
//	        fmt.Println(err)
//	        break
93 94
//	    }
//	}
95
//	if err := sw.Flush(); err != nil {
96
//	    fmt.Println(err)
xurime's avatar
xurime 已提交
97
//	    return
98
//	}
99
//	if err := f.SaveAs("Book1.xlsx"); err != nil {
100 101
//	    fmt.Println(err)
//	}
102
//
103 104
// Set cell value and cell formula for a worksheet with stream writer:
//
105
//	err := sw.SetRow("A1", []interface{}{
106 107 108
//	    excelize.Cell{Value: 1},
//	    excelize.Cell{Value: 2},
//	    excelize.Cell{Formula: "SUM(A1,B1)"}});
109
//
110 111
// Set cell value and rows style for a worksheet with stream writer:
//
112
//	err := sw.SetRow("A1", []interface{}{
113 114
//	    excelize.Cell{Value: 1}},
//	    excelize.RowOpts{StyleID: styleID, Height: 20, Hidden: false});
115
func (f *File) NewStreamWriter(sheet string) (*StreamWriter, error) {
116 117 118
	if err := checkSheetName(sheet); err != nil {
		return nil, err
	}
119
	sheetID := f.getSheetID(sheet)
xurime's avatar
xurime 已提交
120
	if sheetID == -1 {
121
		return nil, ErrSheetNotExist{sheet}
122
	}
C
Cameron Howey 已提交
123
	sw := &StreamWriter{
124
		file:    f,
125 126 127
		Sheet:   sheet,
		SheetID: sheetID,
	}
128 129
	var err error
	sw.worksheet, err = f.workSheetReader(sheet)
C
Cameron Howey 已提交
130 131 132
	if err != nil {
		return nil, err
	}
133

134
	sheetXMLPath, _ := f.getSheetXMLPath(sheet)
135 136 137
	if f.streams == nil {
		f.streams = make(map[string]*StreamWriter)
	}
138
	f.streams[sheetXMLPath] = sw
139

140
	_, _ = sw.rawData.WriteString(xml.Header + `<worksheet` + templateNamespaceIDMap)
141
	bulkAppendFields(&sw.rawData, sw.worksheet, 2, 3)
142
	return sw, err
143 144
}

C
Cameron Howey 已提交
145
// AddTable creates an Excel table for the StreamWriter using the given
146
// cell range and format set. For example, create a table of A1:D5:
147
//
148
//	err := sw.AddTable(&excelize.Table{Range: "A1:D5"})
149
//
C
Cameron Howey 已提交
150 151
// Create a table of F2:H6 with format set:
//
152
//	disable := false
153 154
//	err := sw.AddTable(&excelize.Table{
//	    Range:             "F2:H6",
155 156 157 158 159 160 161
//	    Name:              "table",
//	    StyleName:         "TableStyleMedium2",
//	    ShowFirstColumn:   true,
//	    ShowLastColumn:    true,
//	    ShowRowStripes:    &disable,
//	    ShowColumnStripes: true,
//	})
C
Cameron Howey 已提交
162 163 164 165
//
// Note that the table must be at least two lines including the header. The
// header cells must contain strings and must be unique.
//
166
// Currently, only one table is allowed for a StreamWriter. AddTable must be
C
Cameron Howey 已提交
167 168 169
// called after the rows are written but before Flush.
//
// See File.AddTable for details on the table format.
170 171
func (sw *StreamWriter) AddTable(table *Table) error {
	options, err := parseTableOptions(table)
172 173 174
	if err != nil {
		return err
	}
175
	coordinates, err := rangeRefToCoordinates(options.Range)
C
Cameron Howey 已提交
176 177
	if err != nil {
		return err
178
	}
xurime's avatar
xurime 已提交
179
	_ = sortCoordinates(coordinates)
C
Cameron Howey 已提交
180 181 182 183 184 185

	// Correct the minimum number of rows, the table at least two lines.
	if coordinates[1] == coordinates[3] {
		coordinates[3]++
	}

186
	// Correct table reference range, such correct C1:B3 to B1:C3.
187
	ref, err := sw.file.coordinatesToRangeRef(coordinates)
C
Cameron Howey 已提交
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204
	if err != nil {
		return err
	}

	// create table columns using the first row
	tableHeaders, err := sw.getRowValues(coordinates[1], coordinates[0], coordinates[2])
	if err != nil {
		return err
	}
	tableColumn := make([]*xlsxTableColumn, len(tableHeaders))
	for i, name := range tableHeaders {
		tableColumn[i] = &xlsxTableColumn{
			ID:   i + 1,
			Name: name,
		}
	}

205
	tableID := sw.file.countTables() + 1
C
Cameron Howey 已提交
206

207
	name := options.Name
C
Cameron Howey 已提交
208 209 210 211
	if name == "" {
		name = "Table" + strconv.Itoa(tableID)
	}

212
	tbl := xlsxTable{
213
		XMLNS:       NameSpaceSpreadSheet.Value,
C
Cameron Howey 已提交
214 215 216 217 218 219 220 221 222 223 224 225
		ID:          tableID,
		Name:        name,
		DisplayName: name,
		Ref:         ref,
		AutoFilter: &xlsxAutoFilter{
			Ref: ref,
		},
		TableColumns: &xlsxTableColumns{
			Count:       len(tableColumn),
			TableColumn: tableColumn,
		},
		TableStyleInfo: &xlsxTableStyleInfo{
226
			Name:              options.StyleName,
227 228
			ShowFirstColumn:   options.ShowFirstColumn,
			ShowLastColumn:    options.ShowLastColumn,
229
			ShowRowStripes:    *options.ShowRowStripes,
230
			ShowColumnStripes: options.ShowColumnStripes,
C
Cameron Howey 已提交
231 232 233 234
		},
	}

	sheetRelationshipsTableXML := "../tables/table" + strconv.Itoa(tableID) + ".xml"
235
	tableXML := strings.ReplaceAll(sheetRelationshipsTableXML, "..", "xl")
C
Cameron Howey 已提交
236

237 238
	// Add first table for given sheet
	sheetPath := sw.file.sheetMap[sw.Sheet]
C
Cameron Howey 已提交
239
	sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetPath, "xl/worksheets/") + ".rels"
240
	rID := sw.file.addRels(sheetRels, SourceRelationshipTable, sheetRelationshipsTableXML, "")
C
Cameron Howey 已提交
241 242 243

	sw.tableParts = fmt.Sprintf(`<tableParts count="1"><tablePart r:id="rId%d"></tablePart></tableParts>`, rID)

244
	if err = sw.file.addContentTypePart(tableID, "table"); err != nil {
245 246
		return err
	}
247
	b, _ := xml.Marshal(tbl)
248
	sw.file.saveFileList(tableXML, b)
249
	return err
C
Cameron Howey 已提交
250 251 252
}

// Extract values from a row in the StreamWriter.
253 254
func (sw *StreamWriter) getRowValues(hRow, hCol, vCol int) (res []string, err error) {
	res = make([]string, vCol-hCol+1)
C
Cameron Howey 已提交
255 256 257 258 259 260

	r, err := sw.rawData.Reader()
	if err != nil {
		return nil, err
	}

261
	dec := sw.file.xmlNewDecoder(r)
C
Cameron Howey 已提交
262 263 264 265 266
	for {
		token, err := dec.Token()
		if err == io.EOF {
			return res, nil
		}
267
		if err != nil {
C
Cameron Howey 已提交
268
			return nil, err
269
		}
270
		startElement, ok := getRowElement(token, hRow)
C
Cameron Howey 已提交
271 272
		if !ok {
			continue
273
		}
C
Cameron Howey 已提交
274 275 276 277 278 279 280
		// decode cells
		var row xlsxRow
		if err := dec.DecodeElement(&row, &startElement); err != nil {
			return nil, err
		}
		for _, c := range row.C {
			col, _, err := CellNameToCoordinates(c.R)
281
			if err != nil {
C
Cameron Howey 已提交
282
				return nil, err
283
			}
284
			if col < hCol || col > vCol {
C
Cameron Howey 已提交
285 286
				continue
			}
287
			res[col-hCol], _ = c.getValueFrom(sw.file, nil, false)
288
		}
C
Cameron Howey 已提交
289 290 291 292 293
		return res, nil
	}
}

// Check if the token is an XLSX row with the matching row number.
294
func getRowElement(token xml.Token, hRow int) (startElement xml.StartElement, ok bool) {
C
Cameron Howey 已提交
295 296 297 298 299 300 301 302 303 304 305 306 307 308
	startElement, ok = token.(xml.StartElement)
	if !ok {
		return
	}
	ok = startElement.Name.Local == "row"
	if !ok {
		return
	}
	ok = false
	for _, attr := range startElement.Attr {
		if attr.Name.Local != "r" {
			continue
		}
		row, _ := strconv.Atoi(attr.Value)
309
		if row == hRow {
C
Cameron Howey 已提交
310 311
			ok = true
			return
312 313
		}
	}
C
Cameron Howey 已提交
314
	return
315 316
}

C
Cameron Howey 已提交
317 318 319 320
// Cell can be used directly in StreamWriter.SetRow to specify a style and
// a value.
type Cell struct {
	StyleID int
321
	Formula string
xurime's avatar
xurime 已提交
322
	Value   interface{}
C
Cameron Howey 已提交
323
}
324

325 326
// RowOpts define the options for the set row, it can be used directly in
// StreamWriter.SetRow to specify the style and properties of the row.
327
type RowOpts struct {
328 329 330 331
	Height       float64
	Hidden       bool
	StyleID      int
	OutlineLevel int
332 333
}

334
// marshalAttrs prepare attributes of the row.
335 336 337 338 339
func (r *RowOpts) marshalAttrs() (strings.Builder, error) {
	var (
		err   error
		attrs strings.Builder
	)
340
	if r == nil {
341
		return attrs, err
342 343 344
	}
	if r.Height > MaxRowHeight {
		err = ErrMaxRowHeight
345
		return attrs, err
346
	}
347 348 349 350
	if r.OutlineLevel > 7 {
		err = ErrOutlineLevel
		return attrs, err
	}
351
	if r.StyleID > 0 {
352 353 354
		attrs.WriteString(` s="`)
		attrs.WriteString(strconv.Itoa(r.StyleID))
		attrs.WriteString(`" customFormat="1"`)
355 356
	}
	if r.Height > 0 {
357 358 359
		attrs.WriteString(` ht="`)
		attrs.WriteString(strconv.FormatFloat(r.Height, 'f', -1, 64))
		attrs.WriteString(`" customHeight="1"`)
360
	}
361 362 363 364 365
	if r.OutlineLevel > 0 {
		attrs.WriteString(` outlineLevel="`)
		attrs.WriteString(strconv.Itoa(r.OutlineLevel))
		attrs.WriteString(`"`)
	}
366
	if r.Hidden {
367
		attrs.WriteString(` hidden="1"`)
368
	}
369
	return attrs, err
370 371 372 373 374 375 376 377 378 379 380 381
}

// parseRowOpts provides a function to parse the optional settings for
// *StreamWriter.SetRow.
func parseRowOpts(opts ...RowOpts) *RowOpts {
	options := &RowOpts{}
	for _, opt := range opts {
		options = &opt
	}
	return options
}

382 383 384
// SetRow writes an array to stream rows by giving starting cell reference and a
// pointer to an array of values. Note that you must call the 'Flush' function
// to end the streaming writing process.
C
Cameron Howey 已提交
385 386 387
//
// As a special case, if Cell is used as a value, then the Cell.StyleID will be
// applied to that cell.
388 389
func (sw *StreamWriter) SetRow(cell string, values []interface{}, opts ...RowOpts) error {
	col, row, err := CellNameToCoordinates(cell)
390 391 392
	if err != nil {
		return err
	}
393 394 395 396
	if row <= sw.rows {
		return newStreamSetRowError(row)
	}
	sw.rows = row
397
	sw.writeSheetData()
398 399
	options := parseRowOpts(opts...)
	attrs, err := options.marshalAttrs()
400 401 402
	if err != nil {
		return err
	}
403 404 405 406 407
	_, _ = sw.rawData.WriteString(`<row r="`)
	_, _ = sw.rawData.WriteString(strconv.Itoa(row))
	_, _ = sw.rawData.WriteString(`"`)
	_, _ = sw.rawData.WriteString(attrs.String())
	_, _ = sw.rawData.WriteString(`>`)
C
Cameron Howey 已提交
408
	for i, val := range values {
409 410 411
		if val == nil {
			continue
		}
412
		ref, err := CoordinatesToCellName(col+i, row)
413 414 415
		if err != nil {
			return err
		}
416
		c := xlsxC{R: ref, S: options.StyleID}
C
Cameron Howey 已提交
417 418 419
		if v, ok := val.(Cell); ok {
			c.S = v.StyleID
			val = v.Value
420
			setCellFormula(&c, v.Formula)
C
Cameron Howey 已提交
421 422 423
		} else if v, ok := val.(*Cell); ok && v != nil {
			c.S = v.StyleID
			val = v.Value
424
			setCellFormula(&c, v.Formula)
425
		}
426
		if err = sw.setCellValFunc(&c, val); err != nil {
427
			_, _ = sw.rawData.WriteString(`</row>`)
428 429
			return err
		}
C
Cameron Howey 已提交
430
		writeCell(&sw.rawData, c)
431
	}
432
	_, _ = sw.rawData.WriteString(`</row>`)
C
Cameron Howey 已提交
433 434
	return sw.rawData.Sync()
}
435

436
// SetColWidth provides a function to set the width of a single column or
437
// multiple columns for the StreamWriter. Note that you must call
438 439 440
// the 'SetColWidth' function before the 'SetRow' function. For example set
// the width column B:C as 20:
//
441
//	err := sw.SetColWidth(2, 3, 20)
442 443 444 445
func (sw *StreamWriter) SetColWidth(min, max int, width float64) error {
	if sw.sheetWritten {
		return ErrStreamSetColWidth
	}
446
	if min < MinColumns || min > MaxColumns || max < MinColumns || max > MaxColumns {
447 448 449 450 451 452 453 454
		return ErrColumnNumber
	}
	if width > MaxColumnWidth {
		return ErrColumnWidth
	}
	if min > max {
		min, max = max, min
	}
455 456 457 458 459 460 461 462

	sw.cols.WriteString(`<col min="`)
	sw.cols.WriteString(strconv.Itoa(min))
	sw.cols.WriteString(`" max="`)
	sw.cols.WriteString(strconv.Itoa(max))
	sw.cols.WriteString(`" width="`)
	sw.cols.WriteString(strconv.FormatFloat(width, 'f', -1, 64))
	sw.cols.WriteString(`" customWidth="1"/>`)
463 464 465
	return nil
}

466 467 468 469
// InsertPageBreak creates a page break to determine where the printed page ends
// and where begins the next one by a given cell reference, the content before
// the page break will be printed on one page and after the page break on
// another.
470 471 472 473
func (sw *StreamWriter) InsertPageBreak(cell string) error {
	return sw.worksheet.insertPageBreak(cell)
}

474
// SetPanes provides a function to create and remove freeze panes and split
475 476
// panes by giving panes options for the StreamWriter. Note that you must call
// the 'SetPanes' function before the 'SetRow' function.
477
func (sw *StreamWriter) SetPanes(panes *Panes) error {
478 479 480 481 482 483
	if sw.sheetWritten {
		return ErrStreamSetPanes
	}
	return sw.worksheet.setPanes(panes)
}

484
// MergeCell provides a function to merge cells by a given range reference for
485 486
// the StreamWriter. Don't create a merged cell that overlaps with another
// existing merged cell.
487 488
func (sw *StreamWriter) MergeCell(topLeftCell, bottomRightCell string) error {
	_, err := cellRefsToCoordinates(topLeftCell, bottomRightCell)
489 490 491 492
	if err != nil {
		return err
	}
	sw.mergeCellsCount++
493
	_, _ = sw.mergeCells.WriteString(`<mergeCell ref="`)
494
	_, _ = sw.mergeCells.WriteString(topLeftCell)
495
	_, _ = sw.mergeCells.WriteString(`:`)
496
	_, _ = sw.mergeCells.WriteString(bottomRightCell)
497
	_, _ = sw.mergeCells.WriteString(`"/>`)
498 499 500
	return nil
}

501 502 503
// setCellFormula provides a function to set formula of a cell.
func setCellFormula(c *xlsxC, formula string) {
	if formula != "" {
504
		c.T, c.F = "str", &xlsxF{Content: formula}
505 506 507
	}
}

508 509 510
// setCellTime provides a function to set number of a cell with a time.
func (sw *StreamWriter) setCellTime(c *xlsxC, val time.Time) error {
	var date1904, isNum bool
511
	wb, err := sw.file.workbookReader()
512 513 514 515 516 517 518
	if err != nil {
		return err
	}
	if wb != nil && wb.WorkbookPr != nil {
		date1904 = wb.WorkbookPr.Date1904
	}
	if isNum, err = c.setCellTime(val, date1904); err == nil && isNum && c.S == 0 {
519
		style, _ := sw.file.NewStyle(&Style{NumFmt: 22})
520 521 522 523 524
		c.S = style
	}
	return nil
}

xurime's avatar
xurime 已提交
525
// setCellValFunc provides a function to set value of a cell.
526 527
func (sw *StreamWriter) setCellValFunc(c *xlsxC, val interface{}) error {
	var err error
xurime's avatar
xurime 已提交
528 529 530 531 532 533 534 535
	switch val := val.(type) {
	case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
		err = setCellIntFunc(c, val)
	case float32:
		c.T, c.V = setCellFloat(float64(val), -1, 32)
	case float64:
		c.T, c.V = setCellFloat(val, -1, 64)
	case string:
536
		c.setCellValue(val)
xurime's avatar
xurime 已提交
537
	case []byte:
538
		c.setCellValue(string(val))
xurime's avatar
xurime 已提交
539 540 541
	case time.Duration:
		c.T, c.V = setCellDuration(val)
	case time.Time:
542
		err = sw.setCellTime(c, val)
xurime's avatar
xurime 已提交
543 544 545
	case bool:
		c.T, c.V = setCellBool(val)
	case nil:
546
		return err
547 548 549
	case []RichTextRun:
		c.T, c.IS = "inlineStr", &xlsxSI{}
		c.IS.R, err = setRichText(val)
xurime's avatar
xurime 已提交
550
	default:
551
		c.setCellValue(fmt.Sprint(val))
xurime's avatar
xurime 已提交
552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569
	}
	return err
}

// setCellIntFunc is a wrapper of SetCellInt.
func setCellIntFunc(c *xlsxC, val interface{}) (err error) {
	switch val := val.(type) {
	case int:
		c.T, c.V = setCellInt(val)
	case int8:
		c.T, c.V = setCellInt(int(val))
	case int16:
		c.T, c.V = setCellInt(int(val))
	case int32:
		c.T, c.V = setCellInt(int(val))
	case int64:
		c.T, c.V = setCellInt(int(val))
	case uint:
xurime's avatar
xurime 已提交
570
		c.T, c.V = setCellUint(uint64(val))
xurime's avatar
xurime 已提交
571
	case uint8:
xurime's avatar
xurime 已提交
572
		c.T, c.V = setCellUint(uint64(val))
xurime's avatar
xurime 已提交
573
	case uint16:
xurime's avatar
xurime 已提交
574
		c.T, c.V = setCellUint(uint64(val))
xurime's avatar
xurime 已提交
575
	case uint32:
xurime's avatar
xurime 已提交
576
		c.T, c.V = setCellUint(uint64(val))
xurime's avatar
xurime 已提交
577
	case uint64:
xurime's avatar
xurime 已提交
578
		c.T, c.V = setCellUint(val)
xurime's avatar
xurime 已提交
579 580 581 582 583
	default:
	}
	return
}

584
// writeCell constructs a cell XML and writes it to the buffer.
C
Cameron Howey 已提交
585
func writeCell(buf *bufferedWriter, c xlsxC) {
586
	_, _ = buf.WriteString(`<c`)
C
Cameron Howey 已提交
587
	if c.XMLSpace.Value != "" {
588 589 590 591 592 593 594 595 596
		_, _ = buf.WriteString(` xml:`)
		_, _ = buf.WriteString(c.XMLSpace.Name.Local)
		_, _ = buf.WriteString(`="`)
		_, _ = buf.WriteString(c.XMLSpace.Value)
		_, _ = buf.WriteString(`"`)
	}
	_, _ = buf.WriteString(` r="`)
	_, _ = buf.WriteString(c.R)
	_, _ = buf.WriteString(`"`)
C
Cameron Howey 已提交
597
	if c.S != 0 {
598 599 600
		_, _ = buf.WriteString(` s="`)
		_, _ = buf.WriteString(strconv.Itoa(c.S))
		_, _ = buf.WriteString(`"`)
C
Cameron Howey 已提交
601 602
	}
	if c.T != "" {
603 604 605
		_, _ = buf.WriteString(` t="`)
		_, _ = buf.WriteString(c.T)
		_, _ = buf.WriteString(`"`)
C
Cameron Howey 已提交
606
	}
607
	_, _ = buf.WriteString(`>`)
608 609 610 611 612
	if c.F != nil {
		_, _ = buf.WriteString(`<f>`)
		_ = xml.EscapeText(buf, []byte(c.F.Content))
		_, _ = buf.WriteString(`</f>`)
	}
C
Cameron Howey 已提交
613
	if c.V != "" {
614 615 616
		_, _ = buf.WriteString(`<v>`)
		_ = xml.EscapeText(buf, []byte(c.V))
		_, _ = buf.WriteString(`</v>`)
C
Cameron Howey 已提交
617
	}
618
	if c.IS != nil {
619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637
		if len(c.IS.R) > 0 {
			is, _ := xml.Marshal(c.IS.R)
			_, _ = buf.WriteString(`<is>`)
			_, _ = buf.Write(is)
			_, _ = buf.WriteString(`</is>`)
		}
		if c.IS.T != nil {
			_, _ = buf.WriteString(`<is><t`)
			if c.IS.T.Space.Value != "" {
				_, _ = buf.WriteString(` xml:`)
				_, _ = buf.WriteString(c.IS.T.Space.Name.Local)
				_, _ = buf.WriteString(`="`)
				_, _ = buf.WriteString(c.IS.T.Space.Value)
				_, _ = buf.WriteString(`"`)
			}
			_, _ = buf.WriteString(`>`)
			_, _ = buf.Write([]byte(c.IS.T.Val))
			_, _ = buf.WriteString(`</t></is>`)
		}
638
	}
639
	_, _ = buf.WriteString(`</c>`)
640 641
}

642 643 644
// writeSheetData prepares the element preceding sheetData and writes the
// sheetData XML start element to the buffer.
func (sw *StreamWriter) writeSheetData() {
645
	if !sw.sheetWritten {
646
		bulkAppendFields(&sw.rawData, sw.worksheet, 4, 5)
647 648 649 650
		if sw.cols.Len() > 0 {
			_, _ = sw.rawData.WriteString("<cols>")
			_, _ = sw.rawData.WriteString(sw.cols.String())
			_, _ = sw.rawData.WriteString("</cols>")
651
		}
652 653 654
		_, _ = sw.rawData.WriteString(`<sheetData>`)
		sw.sheetWritten = true
	}
655 656 657 658 659
}

// Flush ending the streaming writing process.
func (sw *StreamWriter) Flush() error {
	sw.writeSheetData()
660
	_, _ = sw.rawData.WriteString(`</sheetData>`)
661
	bulkAppendFields(&sw.rawData, sw.worksheet, 8, 15)
662
	mergeCells := strings.Builder{}
663
	if sw.mergeCellsCount > 0 {
664 665 666 667 668
		_, _ = mergeCells.WriteString(`<mergeCells count="`)
		_, _ = mergeCells.WriteString(strconv.Itoa(sw.mergeCellsCount))
		_, _ = mergeCells.WriteString(`">`)
		_, _ = mergeCells.WriteString(sw.mergeCells.String())
		_, _ = mergeCells.WriteString(`</mergeCells>`)
669
	}
670
	_, _ = sw.rawData.WriteString(mergeCells.String())
671
	bulkAppendFields(&sw.rawData, sw.worksheet, 17, 38)
672
	_, _ = sw.rawData.WriteString(sw.tableParts)
xurime's avatar
xurime 已提交
673
	bulkAppendFields(&sw.rawData, sw.worksheet, 40, 40)
674
	_, _ = sw.rawData.WriteString(`</worksheet>`)
C
Cameron Howey 已提交
675 676 677 678
	if err := sw.rawData.Flush(); err != nil {
		return err
	}

679
	sheetPath := sw.file.sheetMap[sw.Sheet]
680
	sw.file.Sheet.Delete(sheetPath)
xurime's avatar
xurime 已提交
681
	sw.file.checked.Delete(sheetPath)
682
	sw.file.Pkg.Delete(sheetPath)
C
Cameron Howey 已提交
683 684

	return nil
685 686
}

687 688 689
// bulkAppendFields bulk-appends fields in a worksheet by specified field
// names order range.
func bulkAppendFields(w io.Writer, ws *xlsxWorksheet, from, to int) {
690
	s := reflect.ValueOf(ws).Elem()
C
Cameron Howey 已提交
691
	enc := xml.NewEncoder(w)
692
	for i := 0; i < s.NumField(); i++ {
693
		if from <= i && i <= to {
694
			_ = enc.Encode(s.Field(i).Interface())
695 696 697 698
		}
	}
}

C
Cameron Howey 已提交
699 700
// bufferedWriter uses a temp file to store an extended buffer. Writes are
// always made to an in-memory buffer, which will always succeed. The buffer
xurime's avatar
xurime 已提交
701 702
// is written to the temp file with Sync, which may return an error.
// Therefore, Sync should be periodically called and the error checked.
C
Cameron Howey 已提交
703 704 705 706 707
type bufferedWriter struct {
	tmp *os.File
	buf bytes.Buffer
}

708
// Write to the in-memory buffer. The error is always nil.
C
Cameron Howey 已提交
709 710 711 712
func (bw *bufferedWriter) Write(p []byte) (n int, err error) {
	return bw.buf.Write(p)
}

713
// WriteString write to the in-memory buffer. The error is always nil.
C
Cameron Howey 已提交
714 715 716 717 718 719 720 721 722 723 724 725 726 727 728
func (bw *bufferedWriter) WriteString(p string) (n int, err error) {
	return bw.buf.WriteString(p)
}

// Reader provides read-access to the underlying buffer/file.
func (bw *bufferedWriter) Reader() (io.Reader, error) {
	if bw.tmp == nil {
		return bytes.NewReader(bw.buf.Bytes()), nil
	}
	if err := bw.Flush(); err != nil {
		return nil, err
	}
	fi, err := bw.tmp.Stat()
	if err != nil {
		return nil, err
729
	}
C
Cameron Howey 已提交
730 731 732 733
	// os.File.ReadAt does not affect the cursor position and is safe to use here
	return io.NewSectionReader(bw.tmp, 0, fi.Size()), nil
}

xurime's avatar
xurime 已提交
734 735
// Sync will write the in-memory buffer to a temp file, if the in-memory
// buffer has grown large enough. Any error will be returned.
C
Cameron Howey 已提交
736 737
func (bw *bufferedWriter) Sync() (err error) {
	// Try to use local storage
738
	if bw.buf.Len() < StreamChunkSize {
C
Cameron Howey 已提交
739 740 741
		return nil
	}
	if bw.tmp == nil {
742
		bw.tmp, err = os.CreateTemp(os.TempDir(), "excelize-")
C
Cameron Howey 已提交
743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769
		if err != nil {
			// can not use local storage
			return nil
		}
	}
	return bw.Flush()
}

// Flush the entire in-memory buffer to the temp file, if a temp file is being
// used.
func (bw *bufferedWriter) Flush() error {
	if bw.tmp == nil {
		return nil
	}
	_, err := bw.buf.WriteTo(bw.tmp)
	if err != nil {
		return err
	}
	bw.buf.Reset()
	return nil
}

// Close the underlying temp file and reset the in-memory buffer.
func (bw *bufferedWriter) Close() error {
	bw.buf.Reset()
	if bw.tmp == nil {
		return nil
770
	}
C
Cameron Howey 已提交
771 772
	defer os.Remove(bw.tmp.Name())
	return bw.tmp.Close()
773
}