stream.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. // Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
  2. // this source code is governed by a BSD-style license that can be found in
  3. // the LICENSE file.
  4. //
  5. // Package excelize providing a set of functions that allow you to write to
  6. // and read from XLSX files. Support reads and writes XLSX file generated by
  7. // Microsoft Excel™ 2007 and later. Support save file without losing original
  8. // charts of XLSX. This library needs Go version 1.10 or later.
  9. package excelize
  10. import (
  11. "bytes"
  12. "encoding/xml"
  13. "errors"
  14. "fmt"
  15. "io/ioutil"
  16. "os"
  17. "reflect"
  18. )
  19. // StreamWriter defined the type of stream writer.
  20. type StreamWriter struct {
  21. tmpFile *os.File
  22. File *File
  23. Sheet string
  24. SheetID int
  25. SheetData bytes.Buffer
  26. }
  27. // NewStreamWriter return stream writer struct by given worksheet name for
  28. // generate new worksheet with large amounts of data. Note that after set
  29. // rows, you must call the 'Flush' method to end the streaming writing
  30. // process and ensure that the order of line numbers is ascending. For
  31. // example, set data for worksheet of size 102400 rows x 50 columns with
  32. // numbers:
  33. //
  34. // file := excelize.NewFile()
  35. // streamWriter, err := file.NewStreamWriter("Sheet1")
  36. // if err != nil {
  37. // panic(err)
  38. // }
  39. // for rowID := 1; rowID <= 102400; rowID++ {
  40. // row := make([]interface{}, 50)
  41. // for colID := 0; colID < 50; colID++ {
  42. // row[colID] = rand.Intn(640000)
  43. // }
  44. // cell, _ := excelize.CoordinatesToCellName(1, rowID)
  45. // if err := streamWriter.SetRow(cell, &row); err != nil {
  46. // panic(err)
  47. // }
  48. // }
  49. // if err := streamWriter.Flush(); err != nil {
  50. // panic(err)
  51. // }
  52. // if err := file.SaveAs("Book1.xlsx"); err != nil {
  53. // panic(err)
  54. // }
  55. //
  56. func (f *File) NewStreamWriter(sheet string) (*StreamWriter, error) {
  57. sheetID := f.GetSheetIndex(sheet)
  58. if sheetID == 0 {
  59. return nil, fmt.Errorf("sheet %s is not exist", sheet)
  60. }
  61. rsw := &StreamWriter{
  62. File: f,
  63. Sheet: sheet,
  64. SheetID: sheetID,
  65. }
  66. rsw.SheetData.WriteString("<sheetData>")
  67. return rsw, nil
  68. }
  69. // SetRow writes an array to streaming row by given worksheet name, starting
  70. // coordinate and a pointer to array type 'slice'. Note that, cell settings
  71. // with styles are not supported currently and after set rows, you must call the
  72. // 'Flush' method to end the streaming writing process. The following
  73. // shows the supported data types:
  74. //
  75. // int
  76. // string
  77. //
  78. func (sw *StreamWriter) SetRow(axis string, slice interface{}) error {
  79. col, row, err := CellNameToCoordinates(axis)
  80. if err != nil {
  81. return err
  82. }
  83. // Make sure 'slice' is a Ptr to Slice
  84. v := reflect.ValueOf(slice)
  85. if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Slice {
  86. return errors.New("pointer to slice expected")
  87. }
  88. v = v.Elem()
  89. sw.SheetData.WriteString(fmt.Sprintf(`<row r="%d">`, row))
  90. for i := 0; i < v.Len(); i++ {
  91. axis, err := CoordinatesToCellName(col+i, row)
  92. if err != nil {
  93. return err
  94. }
  95. switch val := v.Index(i).Interface().(type) {
  96. case int:
  97. sw.SheetData.WriteString(fmt.Sprintf(`<c r="%s"><v>%d</v></c>`, axis, val))
  98. case string:
  99. sw.SheetData.WriteString(sw.setCellStr(axis, val))
  100. default:
  101. sw.SheetData.WriteString(sw.setCellStr(axis, fmt.Sprint(val)))
  102. }
  103. }
  104. sw.SheetData.WriteString(`</row>`)
  105. // Try to use local storage
  106. chunk := 1 << 24
  107. if sw.SheetData.Len() >= chunk {
  108. if sw.tmpFile == nil {
  109. err := sw.createTmp()
  110. if err != nil {
  111. // can not use local storage
  112. return nil
  113. }
  114. }
  115. // use local storage
  116. _, err := sw.tmpFile.Write(sw.SheetData.Bytes())
  117. if err != nil {
  118. return nil
  119. }
  120. sw.SheetData.Reset()
  121. }
  122. return err
  123. }
  124. // Flush ending the streaming writing process.
  125. func (sw *StreamWriter) Flush() error {
  126. sw.SheetData.WriteString(`</sheetData>`)
  127. ws, err := sw.File.workSheetReader(sw.Sheet)
  128. if err != nil {
  129. return err
  130. }
  131. sheetXML := fmt.Sprintf("xl/worksheets/sheet%d.xml", sw.SheetID)
  132. delete(sw.File.Sheet, sheetXML)
  133. delete(sw.File.checked, sheetXML)
  134. var sheetDataByte []byte
  135. if sw.tmpFile != nil {
  136. // close the local storage file
  137. if err = sw.tmpFile.Close(); err != nil {
  138. return err
  139. }
  140. file, err := os.Open(sw.tmpFile.Name())
  141. if err != nil {
  142. return err
  143. }
  144. sheetDataByte, err = ioutil.ReadAll(file)
  145. if err != nil {
  146. return err
  147. }
  148. if err := file.Close(); err != nil {
  149. return err
  150. }
  151. err = os.Remove(sw.tmpFile.Name())
  152. if err != nil {
  153. return err
  154. }
  155. }
  156. sheetDataByte = append(sheetDataByte, sw.SheetData.Bytes()...)
  157. replaceMap := map[string][]byte{
  158. "XMLName": []byte{},
  159. "SheetData": sheetDataByte,
  160. }
  161. sw.SheetData.Reset()
  162. sw.File.XLSX[fmt.Sprintf("xl/worksheets/sheet%d.xml", sw.SheetID)] =
  163. StreamMarshalSheet(ws, replaceMap)
  164. return err
  165. }
  166. // createTmp creates a temporary file in the operating system default
  167. // temporary directory.
  168. func (sw *StreamWriter) createTmp() (err error) {
  169. sw.tmpFile, err = ioutil.TempFile(os.TempDir(), "excelize-")
  170. return err
  171. }
  172. // StreamMarshalSheet provides method to serialization worksheets by field as
  173. // streaming.
  174. func StreamMarshalSheet(ws *xlsxWorksheet, replaceMap map[string][]byte) []byte {
  175. s := reflect.ValueOf(ws).Elem()
  176. typeOfT := s.Type()
  177. var marshalResult []byte
  178. marshalResult = append(marshalResult, []byte(XMLHeader+`<worksheet`+templateNamespaceIDMap)...)
  179. for i := 0; i < s.NumField(); i++ {
  180. f := s.Field(i)
  181. content, ok := replaceMap[typeOfT.Field(i).Name]
  182. if ok {
  183. marshalResult = append(marshalResult, content...)
  184. continue
  185. }
  186. out, _ := xml.Marshal(f.Interface())
  187. marshalResult = append(marshalResult, out...)
  188. }
  189. marshalResult = append(marshalResult, []byte(`</worksheet>`)...)
  190. return marshalResult
  191. }
  192. // setCellStr provides a function to set string type value of a cell as
  193. // streaming. Total number of characters that a cell can contain 32767
  194. // characters.
  195. func (sw *StreamWriter) setCellStr(axis, value string) string {
  196. if len(value) > 32767 {
  197. value = value[0:32767]
  198. }
  199. // Leading and ending space(s) character detection.
  200. if len(value) > 0 && (value[0] == 32 || value[len(value)-1] == 32) {
  201. return fmt.Sprintf(`<c xml:space="preserve" r="%s" t="str"><v>%s</v></c>`, axis, value)
  202. }
  203. return fmt.Sprintf(`<c r="%s" t="str"><v>%s</v></c>`, axis, value)
  204. }