stream.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539
  1. // Copyright 2016 - 2021 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 / XLSM / XLTM files. Supports reading and writing
  7. // spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
  8. // complex components by high compatibility, and provided streaming API for
  9. // generating or reading data from a worksheet with huge amounts of data. This
  10. // library needs Go version 1.15 or later.
  11. package excelize
  12. import (
  13. "bytes"
  14. "encoding/xml"
  15. "fmt"
  16. "io"
  17. "io/ioutil"
  18. "os"
  19. "reflect"
  20. "strconv"
  21. "strings"
  22. "time"
  23. )
  24. // StreamWriter defined the type of stream writer.
  25. type StreamWriter struct {
  26. File *File
  27. Sheet string
  28. SheetID int
  29. worksheet *xlsxWorksheet
  30. rawData bufferedWriter
  31. mergeCellsCount int
  32. mergeCells string
  33. tableParts string
  34. }
  35. // NewStreamWriter return stream writer struct by given worksheet name for
  36. // generate new worksheet with large amounts of data. Note that after set
  37. // rows, you must call the 'Flush' method to end the streaming writing
  38. // process and ensure that the order of line numbers is ascending, the common
  39. // API and stream API can't be work mixed to writing data on the worksheets,
  40. // you can't get cell value when in-memory chunks data over 16MB. For
  41. // example, set data for worksheet of size 102400 rows x 50 columns with
  42. // numbers and style:
  43. //
  44. // file := excelize.NewFile()
  45. // streamWriter, err := file.NewStreamWriter("Sheet1")
  46. // if err != nil {
  47. // fmt.Println(err)
  48. // }
  49. // styleID, err := file.NewStyle(`{"font":{"color":"#777777"}}`)
  50. // if err != nil {
  51. // fmt.Println(err)
  52. // }
  53. // if err := streamWriter.SetRow("A1", []interface{}{excelize.Cell{StyleID: styleID, Value: "Data"}}); err != nil {
  54. // fmt.Println(err)
  55. // }
  56. // for rowID := 2; rowID <= 102400; rowID++ {
  57. // row := make([]interface{}, 50)
  58. // for colID := 0; colID < 50; colID++ {
  59. // row[colID] = rand.Intn(640000)
  60. // }
  61. // cell, _ := excelize.CoordinatesToCellName(1, rowID)
  62. // if err := streamWriter.SetRow(cell, row); err != nil {
  63. // fmt.Println(err)
  64. // }
  65. // }
  66. // if err := streamWriter.Flush(); err != nil {
  67. // fmt.Println(err)
  68. // }
  69. // if err := file.SaveAs("Book1.xlsx"); err != nil {
  70. // fmt.Println(err)
  71. // }
  72. //
  73. // Set cell value and cell formula for a worksheet with stream writer:
  74. //
  75. // err := streamWriter.SetRow("A1", []interface{}{
  76. // excelize.Cell{Value: 1},
  77. // excelize.Cell{Value: 2},
  78. // excelize.Cell{Formula: "SUM(A1,B1)"}});
  79. //
  80. func (f *File) NewStreamWriter(sheet string) (*StreamWriter, error) {
  81. sheetID := f.getSheetID(sheet)
  82. if sheetID == -1 {
  83. return nil, fmt.Errorf("sheet %s is not exist", sheet)
  84. }
  85. sw := &StreamWriter{
  86. File: f,
  87. Sheet: sheet,
  88. SheetID: sheetID,
  89. }
  90. var err error
  91. sw.worksheet, err = f.workSheetReader(sheet)
  92. if err != nil {
  93. return nil, err
  94. }
  95. sheetXML := fmt.Sprintf("xl/worksheets/sheet%d.xml", sw.SheetID)
  96. if f.streams == nil {
  97. f.streams = make(map[string]*StreamWriter)
  98. }
  99. f.streams[sheetXML] = sw
  100. _, _ = sw.rawData.WriteString(XMLHeader + `<worksheet` + templateNamespaceIDMap)
  101. bulkAppendFields(&sw.rawData, sw.worksheet, 2, 6)
  102. _, _ = sw.rawData.WriteString(`<sheetData>`)
  103. return sw, err
  104. }
  105. // AddTable creates an Excel table for the StreamWriter using the given
  106. // coordinate area and format set. For example, create a table of A1:D5:
  107. //
  108. // err := sw.AddTable("A1", "D5", "")
  109. //
  110. // Create a table of F2:H6 with format set:
  111. //
  112. // err := sw.AddTable("F2", "H6", `{
  113. // "table_name": "table",
  114. // "table_style": "TableStyleMedium2",
  115. // "show_first_column": true,
  116. // "show_last_column": true,
  117. // "show_row_stripes": false,
  118. // "show_column_stripes": true
  119. // }`)
  120. //
  121. // Note that the table must be at least two lines including the header. The
  122. // header cells must contain strings and must be unique.
  123. //
  124. // Currently only one table is allowed for a StreamWriter. AddTable must be
  125. // called after the rows are written but before Flush.
  126. //
  127. // See File.AddTable for details on the table format.
  128. func (sw *StreamWriter) AddTable(hcell, vcell, format string) error {
  129. formatSet, err := parseFormatTableSet(format)
  130. if err != nil {
  131. return err
  132. }
  133. coordinates, err := areaRangeToCoordinates(hcell, vcell)
  134. if err != nil {
  135. return err
  136. }
  137. _ = sortCoordinates(coordinates)
  138. // Correct the minimum number of rows, the table at least two lines.
  139. if coordinates[1] == coordinates[3] {
  140. coordinates[3]++
  141. }
  142. // Correct table reference coordinate area, such correct C1:B3 to B1:C3.
  143. ref, err := sw.File.coordinatesToAreaRef(coordinates)
  144. if err != nil {
  145. return err
  146. }
  147. // create table columns using the first row
  148. tableHeaders, err := sw.getRowValues(coordinates[1], coordinates[0], coordinates[2])
  149. if err != nil {
  150. return err
  151. }
  152. tableColumn := make([]*xlsxTableColumn, len(tableHeaders))
  153. for i, name := range tableHeaders {
  154. tableColumn[i] = &xlsxTableColumn{
  155. ID: i + 1,
  156. Name: name,
  157. }
  158. }
  159. tableID := sw.File.countTables() + 1
  160. name := formatSet.TableName
  161. if name == "" {
  162. name = "Table" + strconv.Itoa(tableID)
  163. }
  164. table := xlsxTable{
  165. XMLNS: NameSpaceSpreadSheet.Value,
  166. ID: tableID,
  167. Name: name,
  168. DisplayName: name,
  169. Ref: ref,
  170. AutoFilter: &xlsxAutoFilter{
  171. Ref: ref,
  172. },
  173. TableColumns: &xlsxTableColumns{
  174. Count: len(tableColumn),
  175. TableColumn: tableColumn,
  176. },
  177. TableStyleInfo: &xlsxTableStyleInfo{
  178. Name: formatSet.TableStyle,
  179. ShowFirstColumn: formatSet.ShowFirstColumn,
  180. ShowLastColumn: formatSet.ShowLastColumn,
  181. ShowRowStripes: formatSet.ShowRowStripes,
  182. ShowColumnStripes: formatSet.ShowColumnStripes,
  183. },
  184. }
  185. sheetRelationshipsTableXML := "../tables/table" + strconv.Itoa(tableID) + ".xml"
  186. tableXML := strings.Replace(sheetRelationshipsTableXML, "..", "xl", -1)
  187. // Add first table for given sheet.
  188. sheetPath := sw.File.sheetMap[trimSheetName(sw.Sheet)]
  189. sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetPath, "xl/worksheets/") + ".rels"
  190. rID := sw.File.addRels(sheetRels, SourceRelationshipTable, sheetRelationshipsTableXML, "")
  191. sw.tableParts = fmt.Sprintf(`<tableParts count="1"><tablePart r:id="rId%d"></tablePart></tableParts>`, rID)
  192. sw.File.addContentTypePart(tableID, "table")
  193. b, _ := xml.Marshal(table)
  194. sw.File.saveFileList(tableXML, b)
  195. return nil
  196. }
  197. // Extract values from a row in the StreamWriter.
  198. func (sw *StreamWriter) getRowValues(hrow, hcol, vcol int) (res []string, err error) {
  199. res = make([]string, vcol-hcol+1)
  200. r, err := sw.rawData.Reader()
  201. if err != nil {
  202. return nil, err
  203. }
  204. dec := sw.File.xmlNewDecoder(r)
  205. for {
  206. token, err := dec.Token()
  207. if err == io.EOF {
  208. return res, nil
  209. }
  210. if err != nil {
  211. return nil, err
  212. }
  213. startElement, ok := getRowElement(token, hrow)
  214. if !ok {
  215. continue
  216. }
  217. // decode cells
  218. var row xlsxRow
  219. if err := dec.DecodeElement(&row, &startElement); err != nil {
  220. return nil, err
  221. }
  222. for _, c := range row.C {
  223. col, _, err := CellNameToCoordinates(c.R)
  224. if err != nil {
  225. return nil, err
  226. }
  227. if col < hcol || col > vcol {
  228. continue
  229. }
  230. res[col-hcol] = c.V
  231. }
  232. return res, nil
  233. }
  234. }
  235. // Check if the token is an XLSX row with the matching row number.
  236. func getRowElement(token xml.Token, hrow int) (startElement xml.StartElement, ok bool) {
  237. startElement, ok = token.(xml.StartElement)
  238. if !ok {
  239. return
  240. }
  241. ok = startElement.Name.Local == "row"
  242. if !ok {
  243. return
  244. }
  245. ok = false
  246. for _, attr := range startElement.Attr {
  247. if attr.Name.Local != "r" {
  248. continue
  249. }
  250. row, _ := strconv.Atoi(attr.Value)
  251. if row == hrow {
  252. ok = true
  253. return
  254. }
  255. }
  256. return
  257. }
  258. // Cell can be used directly in StreamWriter.SetRow to specify a style and
  259. // a value.
  260. type Cell struct {
  261. StyleID int
  262. Formula string
  263. Value interface{}
  264. }
  265. // SetRow writes an array to stream rows by giving a worksheet name, starting
  266. // coordinate and a pointer to an array of values. Note that you must call the
  267. // 'Flush' method to end the streaming writing process.
  268. //
  269. // As a special case, if Cell is used as a value, then the Cell.StyleID will be
  270. // applied to that cell.
  271. func (sw *StreamWriter) SetRow(axis string, values []interface{}) error {
  272. col, row, err := CellNameToCoordinates(axis)
  273. if err != nil {
  274. return err
  275. }
  276. fmt.Fprintf(&sw.rawData, `<row r="%d">`, row)
  277. for i, val := range values {
  278. axis, err := CoordinatesToCellName(col+i, row)
  279. if err != nil {
  280. return err
  281. }
  282. c := xlsxC{R: axis}
  283. if v, ok := val.(Cell); ok {
  284. c.S = v.StyleID
  285. val = v.Value
  286. setCellFormula(&c, v.Formula)
  287. } else if v, ok := val.(*Cell); ok && v != nil {
  288. c.S = v.StyleID
  289. val = v.Value
  290. setCellFormula(&c, v.Formula)
  291. }
  292. if err = setCellValFunc(&c, val); err != nil {
  293. _, _ = sw.rawData.WriteString(`</row>`)
  294. return err
  295. }
  296. writeCell(&sw.rawData, c)
  297. }
  298. _, _ = sw.rawData.WriteString(`</row>`)
  299. return sw.rawData.Sync()
  300. }
  301. // MergeCell provides a function to merge cells by a given coordinate area for
  302. // the StreamWriter. Don't create a merged cell that overlaps with another
  303. // existing merged cell.
  304. func (sw *StreamWriter) MergeCell(hcell, vcell string) error {
  305. _, err := areaRangeToCoordinates(hcell, vcell)
  306. if err != nil {
  307. return err
  308. }
  309. sw.mergeCellsCount++
  310. sw.mergeCells += fmt.Sprintf(`<mergeCell ref="%s:%s"/>`, hcell, vcell)
  311. return nil
  312. }
  313. // setCellFormula provides a function to set formula of a cell.
  314. func setCellFormula(c *xlsxC, formula string) {
  315. if formula != "" {
  316. c.F = &xlsxF{Content: formula}
  317. }
  318. }
  319. // setCellValFunc provides a function to set value of a cell.
  320. func setCellValFunc(c *xlsxC, val interface{}) (err error) {
  321. switch val := val.(type) {
  322. case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
  323. err = setCellIntFunc(c, val)
  324. case float32:
  325. c.T, c.V = setCellFloat(float64(val), -1, 32)
  326. case float64:
  327. c.T, c.V = setCellFloat(val, -1, 64)
  328. case string:
  329. c.T, c.V, c.XMLSpace = setCellStr(val)
  330. case []byte:
  331. c.T, c.V, c.XMLSpace = setCellStr(string(val))
  332. case time.Duration:
  333. c.T, c.V = setCellDuration(val)
  334. case time.Time:
  335. c.T, c.V, _, err = setCellTime(val)
  336. case bool:
  337. c.T, c.V = setCellBool(val)
  338. case nil:
  339. c.T, c.V, c.XMLSpace = setCellStr("")
  340. default:
  341. c.T, c.V, c.XMLSpace = setCellStr(fmt.Sprint(val))
  342. }
  343. return err
  344. }
  345. // setCellIntFunc is a wrapper of SetCellInt.
  346. func setCellIntFunc(c *xlsxC, val interface{}) (err error) {
  347. switch val := val.(type) {
  348. case int:
  349. c.T, c.V = setCellInt(val)
  350. case int8:
  351. c.T, c.V = setCellInt(int(val))
  352. case int16:
  353. c.T, c.V = setCellInt(int(val))
  354. case int32:
  355. c.T, c.V = setCellInt(int(val))
  356. case int64:
  357. c.T, c.V = setCellInt(int(val))
  358. case uint:
  359. c.T, c.V = setCellInt(int(val))
  360. case uint8:
  361. c.T, c.V = setCellInt(int(val))
  362. case uint16:
  363. c.T, c.V = setCellInt(int(val))
  364. case uint32:
  365. c.T, c.V = setCellInt(int(val))
  366. case uint64:
  367. c.T, c.V = setCellInt(int(val))
  368. default:
  369. }
  370. return
  371. }
  372. func writeCell(buf *bufferedWriter, c xlsxC) {
  373. _, _ = buf.WriteString(`<c`)
  374. if c.XMLSpace.Value != "" {
  375. fmt.Fprintf(buf, ` xml:%s="%s"`, c.XMLSpace.Name.Local, c.XMLSpace.Value)
  376. }
  377. fmt.Fprintf(buf, ` r="%s"`, c.R)
  378. if c.S != 0 {
  379. fmt.Fprintf(buf, ` s="%d"`, c.S)
  380. }
  381. if c.T != "" {
  382. fmt.Fprintf(buf, ` t="%s"`, c.T)
  383. }
  384. _, _ = buf.WriteString(`>`)
  385. if c.F != nil {
  386. _, _ = buf.WriteString(`<f>`)
  387. _ = xml.EscapeText(buf, []byte(c.F.Content))
  388. _, _ = buf.WriteString(`</f>`)
  389. }
  390. if c.V != "" {
  391. _, _ = buf.WriteString(`<v>`)
  392. _ = xml.EscapeText(buf, []byte(c.V))
  393. _, _ = buf.WriteString(`</v>`)
  394. }
  395. _, _ = buf.WriteString(`</c>`)
  396. }
  397. // Flush ending the streaming writing process.
  398. func (sw *StreamWriter) Flush() error {
  399. _, _ = sw.rawData.WriteString(`</sheetData>`)
  400. bulkAppendFields(&sw.rawData, sw.worksheet, 8, 15)
  401. if sw.mergeCellsCount > 0 {
  402. sw.mergeCells = fmt.Sprintf(`<mergeCells count="%d">%s</mergeCells>`, sw.mergeCellsCount, sw.mergeCells)
  403. }
  404. _, _ = sw.rawData.WriteString(sw.mergeCells)
  405. bulkAppendFields(&sw.rawData, sw.worksheet, 17, 38)
  406. _, _ = sw.rawData.WriteString(sw.tableParts)
  407. bulkAppendFields(&sw.rawData, sw.worksheet, 40, 40)
  408. _, _ = sw.rawData.WriteString(`</worksheet>`)
  409. if err := sw.rawData.Flush(); err != nil {
  410. return err
  411. }
  412. sheetXML := fmt.Sprintf("xl/worksheets/sheet%d.xml", sw.SheetID)
  413. delete(sw.File.Sheet, sheetXML)
  414. delete(sw.File.checked, sheetXML)
  415. delete(sw.File.XLSX, sheetXML)
  416. return nil
  417. }
  418. // bulkAppendFields bulk-appends fields in a worksheet by specified field
  419. // names order range.
  420. func bulkAppendFields(w io.Writer, ws *xlsxWorksheet, from, to int) {
  421. s := reflect.ValueOf(ws).Elem()
  422. enc := xml.NewEncoder(w)
  423. for i := 0; i < s.NumField(); i++ {
  424. if from <= i && i <= to {
  425. _ = enc.Encode(s.Field(i).Interface())
  426. }
  427. }
  428. }
  429. // bufferedWriter uses a temp file to store an extended buffer. Writes are
  430. // always made to an in-memory buffer, which will always succeed. The buffer
  431. // is written to the temp file with Sync, which may return an error.
  432. // Therefore, Sync should be periodically called and the error checked.
  433. type bufferedWriter struct {
  434. tmp *os.File
  435. buf bytes.Buffer
  436. }
  437. // Write to the in-memory buffer. The err is always nil.
  438. func (bw *bufferedWriter) Write(p []byte) (n int, err error) {
  439. return bw.buf.Write(p)
  440. }
  441. // WriteString wites to the in-memory buffer. The err is always nil.
  442. func (bw *bufferedWriter) WriteString(p string) (n int, err error) {
  443. return bw.buf.WriteString(p)
  444. }
  445. // Reader provides read-access to the underlying buffer/file.
  446. func (bw *bufferedWriter) Reader() (io.Reader, error) {
  447. if bw.tmp == nil {
  448. return bytes.NewReader(bw.buf.Bytes()), nil
  449. }
  450. if err := bw.Flush(); err != nil {
  451. return nil, err
  452. }
  453. fi, err := bw.tmp.Stat()
  454. if err != nil {
  455. return nil, err
  456. }
  457. // os.File.ReadAt does not affect the cursor position and is safe to use here
  458. return io.NewSectionReader(bw.tmp, 0, fi.Size()), nil
  459. }
  460. // Sync will write the in-memory buffer to a temp file, if the in-memory
  461. // buffer has grown large enough. Any error will be returned.
  462. func (bw *bufferedWriter) Sync() (err error) {
  463. // Try to use local storage
  464. if bw.buf.Len() < StreamChunkSize {
  465. return nil
  466. }
  467. if bw.tmp == nil {
  468. bw.tmp, err = ioutil.TempFile(os.TempDir(), "excelize-")
  469. if err != nil {
  470. // can not use local storage
  471. return nil
  472. }
  473. }
  474. return bw.Flush()
  475. }
  476. // Flush the entire in-memory buffer to the temp file, if a temp file is being
  477. // used.
  478. func (bw *bufferedWriter) Flush() error {
  479. if bw.tmp == nil {
  480. return nil
  481. }
  482. _, err := bw.buf.WriteTo(bw.tmp)
  483. if err != nil {
  484. return err
  485. }
  486. bw.buf.Reset()
  487. return nil
  488. }
  489. // Close the underlying temp file and reset the in-memory buffer.
  490. func (bw *bufferedWriter) Close() error {
  491. bw.buf.Reset()
  492. if bw.tmp == nil {
  493. return nil
  494. }
  495. defer os.Remove(bw.tmp.Name())
  496. return bw.tmp.Close()
  497. }