| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219 |
- // Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
- // this source code is governed by a BSD-style license that can be found in
- // the LICENSE file.
- //
- // Package excelize providing a set of functions that allow you to write to
- // and read from XLSX files. Support reads and writes XLSX file generated by
- // Microsoft Excel™ 2007 and later. Support save file without losing original
- // charts of XLSX. This library needs Go version 1.10 or later.
- package excelize
- import (
- "bytes"
- "encoding/xml"
- "errors"
- "fmt"
- "io/ioutil"
- "os"
- "reflect"
- )
- // StreamWriter defined the type of stream writer.
- type StreamWriter struct {
- tmpFile *os.File
- File *File
- Sheet string
- SheetID int
- SheetData bytes.Buffer
- }
- // NewStreamWriter return stream writer struct by given worksheet name for
- // generate new worksheet with large amounts of data. Note that after set
- // rows, you must call the 'Flush' method to end the streaming writing
- // process and ensure that the order of line numbers is ascending. For
- // example, set data for worksheet of size 102400 rows x 50 columns with
- // numbers:
- //
- // file := excelize.NewFile()
- // streamWriter, err := file.NewStreamWriter("Sheet1")
- // if err != nil {
- // panic(err)
- // }
- // for rowID := 1; rowID <= 102400; rowID++ {
- // row := make([]interface{}, 50)
- // for colID := 0; colID < 50; colID++ {
- // row[colID] = rand.Intn(640000)
- // }
- // cell, _ := excelize.CoordinatesToCellName(1, rowID)
- // if err := streamWriter.SetRow(cell, &row); err != nil {
- // panic(err)
- // }
- // }
- // if err := streamWriter.Flush(); err != nil {
- // panic(err)
- // }
- // if err := file.SaveAs("Book1.xlsx"); err != nil {
- // panic(err)
- // }
- //
- func (f *File) NewStreamWriter(sheet string) (*StreamWriter, error) {
- sheetID := f.GetSheetIndex(sheet)
- if sheetID == 0 {
- return nil, fmt.Errorf("sheet %s is not exist", sheet)
- }
- rsw := &StreamWriter{
- File: f,
- Sheet: sheet,
- SheetID: sheetID,
- }
- rsw.SheetData.WriteString("<sheetData>")
- return rsw, nil
- }
- // SetRow writes an array to streaming row by given worksheet name, starting
- // coordinate and a pointer to array type 'slice'. Note that, cell settings
- // with styles are not supported currently and after set rows, you must call the
- // 'Flush' method to end the streaming writing process. The following
- // shows the supported data types:
- //
- // int
- // string
- //
- func (sw *StreamWriter) SetRow(axis string, slice interface{}) error {
- col, row, err := CellNameToCoordinates(axis)
- if err != nil {
- return err
- }
- // Make sure 'slice' is a Ptr to Slice
- v := reflect.ValueOf(slice)
- if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Slice {
- return errors.New("pointer to slice expected")
- }
- v = v.Elem()
- sw.SheetData.WriteString(fmt.Sprintf(`<row r="%d">`, row))
- for i := 0; i < v.Len(); i++ {
- axis, err := CoordinatesToCellName(col+i, row)
- if err != nil {
- return err
- }
- switch val := v.Index(i).Interface().(type) {
- case int:
- sw.SheetData.WriteString(fmt.Sprintf(`<c r="%s"><v>%d</v></c>`, axis, val))
- case string:
- sw.SheetData.WriteString(sw.setCellStr(axis, val))
- default:
- sw.SheetData.WriteString(sw.setCellStr(axis, fmt.Sprint(val)))
- }
- }
- sw.SheetData.WriteString(`</row>`)
- // Try to use local storage
- chunk := 1 << 24
- if sw.SheetData.Len() >= chunk {
- if sw.tmpFile == nil {
- err := sw.createTmp()
- if err != nil {
- // can not use local storage
- return nil
- }
- }
- // use local storage
- _, err := sw.tmpFile.Write(sw.SheetData.Bytes())
- if err != nil {
- return nil
- }
- sw.SheetData.Reset()
- }
- return err
- }
- // Flush ending the streaming writing process.
- func (sw *StreamWriter) Flush() error {
- sw.SheetData.WriteString(`</sheetData>`)
- ws, err := sw.File.workSheetReader(sw.Sheet)
- if err != nil {
- return err
- }
- sheetXML := fmt.Sprintf("xl/worksheets/sheet%d.xml", sw.SheetID)
- delete(sw.File.Sheet, sheetXML)
- delete(sw.File.checked, sheetXML)
- var sheetDataByte []byte
- if sw.tmpFile != nil {
- // close the local storage file
- if err = sw.tmpFile.Close(); err != nil {
- return err
- }
- file, err := os.Open(sw.tmpFile.Name())
- if err != nil {
- return err
- }
- sheetDataByte, err = ioutil.ReadAll(file)
- if err != nil {
- return err
- }
- if err := file.Close(); err != nil {
- return err
- }
- err = os.Remove(sw.tmpFile.Name())
- if err != nil {
- return err
- }
- }
- sheetDataByte = append(sheetDataByte, sw.SheetData.Bytes()...)
- replaceMap := map[string][]byte{
- "XMLName": []byte{},
- "SheetData": sheetDataByte,
- }
- sw.SheetData.Reset()
- sw.File.XLSX[fmt.Sprintf("xl/worksheets/sheet%d.xml", sw.SheetID)] =
- StreamMarshalSheet(ws, replaceMap)
- return err
- }
- // createTmp creates a temporary file in the operating system default
- // temporary directory.
- func (sw *StreamWriter) createTmp() (err error) {
- sw.tmpFile, err = ioutil.TempFile(os.TempDir(), "excelize-")
- return err
- }
- // StreamMarshalSheet provides method to serialization worksheets by field as
- // streaming.
- func StreamMarshalSheet(ws *xlsxWorksheet, replaceMap map[string][]byte) []byte {
- s := reflect.ValueOf(ws).Elem()
- typeOfT := s.Type()
- var marshalResult []byte
- marshalResult = append(marshalResult, []byte(XMLHeader+`<worksheet`+templateNamespaceIDMap)...)
- for i := 0; i < s.NumField(); i++ {
- f := s.Field(i)
- content, ok := replaceMap[typeOfT.Field(i).Name]
- if ok {
- marshalResult = append(marshalResult, content...)
- continue
- }
- out, _ := xml.Marshal(f.Interface())
- marshalResult = append(marshalResult, out...)
- }
- marshalResult = append(marshalResult, []byte(`</worksheet>`)...)
- return marshalResult
- }
- // setCellStr provides a function to set string type value of a cell as
- // streaming. Total number of characters that a cell can contain 32767
- // characters.
- func (sw *StreamWriter) setCellStr(axis, value string) string {
- if len(value) > 32767 {
- value = value[0:32767]
- }
- // Leading and ending space(s) character detection.
- if len(value) > 0 && (value[0] == 32 || value[len(value)-1] == 32) {
- return fmt.Sprintf(`<c xml:space="preserve" r="%s" t="str"><v>%s</v></c>`, axis, value)
- }
- return fmt.Sprintf(`<c r="%s" t="str"><v>%s</v></c>`, axis, value)
- }
|