2022-01-09 00:20:42 +08:00
|
|
|
// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of
|
2019-12-10 00:16:17 +08:00
|
|
|
// this source code is governed by a BSD-style license that can be found in
|
|
|
|
// the LICENSE file.
|
|
|
|
//
|
2022-02-17 00:09:11 +08:00
|
|
|
// 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
|
|
|
|
// data. This library needs Go version 1.15 or later.
|
2019-12-10 00:16:17 +08:00
|
|
|
|
|
|
|
package excelize
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"encoding/xml"
|
|
|
|
"fmt"
|
2019-12-29 12:45:10 +08:00
|
|
|
"io"
|
2019-12-10 00:16:17 +08:00
|
|
|
"os"
|
|
|
|
"reflect"
|
2019-12-29 12:45:10 +08:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"time"
|
2019-12-10 00:16:17 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
// StreamWriter defined the type of stream writer.
|
|
|
|
type StreamWriter struct {
|
2022-11-16 00:02:35 +08:00
|
|
|
file *File
|
2021-04-28 10:04:36 +08:00
|
|
|
Sheet string
|
|
|
|
SheetID int
|
2021-05-10 00:09:24 +08:00
|
|
|
sheetWritten bool
|
2022-10-13 00:02:53 +08:00
|
|
|
cols strings.Builder
|
2021-04-28 10:04:36 +08:00
|
|
|
worksheet *xlsxWorksheet
|
|
|
|
rawData bufferedWriter
|
2022-10-20 00:02:30 +08:00
|
|
|
rows int
|
2021-04-28 10:04:36 +08:00
|
|
|
mergeCellsCount int
|
2022-09-23 00:02:45 +08:00
|
|
|
mergeCells strings.Builder
|
2021-04-28 10:04:36 +08:00
|
|
|
tableParts string
|
2019-12-10 00:16:17 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewStreamWriter return stream writer struct by given worksheet name for
|
|
|
|
// generate new worksheet with large amounts of data. Note that after set
|
2022-10-08 22:08:06 +08:00
|
|
|
// rows, you must call the 'Flush' method to end the streaming writing process
|
2022-10-20 00:02:30 +08:00
|
|
|
// and ensure that the order of row numbers is ascending, the normal mode
|
2022-10-08 22:08:06 +08:00
|
|
|
// functions and stream mode functions can't be work mixed to writing data on
|
|
|
|
// the worksheets, you can't get cell value when in-memory chunks data over
|
|
|
|
// 16MB. For example, set data for worksheet of size 102400 rows x 50 columns
|
|
|
|
// with numbers and style:
|
2019-12-10 00:16:17 +08:00
|
|
|
//
|
2022-08-13 11:21:59 +08:00
|
|
|
// file := excelize.NewFile()
|
2022-10-26 00:04:23 +08:00
|
|
|
// defer func() {
|
|
|
|
// if err := file.Close(); err != nil {
|
|
|
|
// fmt.Println(err)
|
|
|
|
// }
|
|
|
|
// }()
|
2022-08-13 11:21:59 +08:00
|
|
|
// streamWriter, err := file.NewStreamWriter("Sheet1")
|
|
|
|
// if err != nil {
|
|
|
|
// fmt.Println(err)
|
|
|
|
// }
|
|
|
|
// styleID, err := file.NewStyle(&excelize.Style{Font: &excelize.Font{Color: "#777777"}})
|
|
|
|
// if err != nil {
|
|
|
|
// fmt.Println(err)
|
|
|
|
// }
|
2022-10-10 00:11:18 +08:00
|
|
|
// if err := streamWriter.SetRow("A1",
|
|
|
|
// []interface{}{
|
|
|
|
// excelize.Cell{StyleID: styleID, Value: "Data"},
|
|
|
|
// []excelize.RichTextRun{
|
|
|
|
// {Text: "Rich ", Font: &excelize.Font{Color: "2354e8"}},
|
|
|
|
// {Text: "Text", Font: &excelize.Font{Color: "e83723"}},
|
|
|
|
// },
|
|
|
|
// },
|
2022-08-13 11:21:59 +08:00
|
|
|
// excelize.RowOpts{Height: 45, Hidden: false}); err != nil {
|
|
|
|
// fmt.Println(err)
|
|
|
|
// }
|
|
|
|
// for rowID := 2; 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 {
|
|
|
|
// fmt.Println(err)
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// if err := streamWriter.Flush(); err != nil {
|
|
|
|
// fmt.Println(err)
|
|
|
|
// }
|
|
|
|
// if err := file.SaveAs("Book1.xlsx"); err != nil {
|
|
|
|
// fmt.Println(err)
|
|
|
|
// }
|
2019-12-10 00:16:17 +08:00
|
|
|
//
|
2021-03-07 15:02:04 +08:00
|
|
|
// Set cell value and cell formula for a worksheet with stream writer:
|
|
|
|
//
|
2022-08-13 11:21:59 +08:00
|
|
|
// err := streamWriter.SetRow("A1", []interface{}{
|
|
|
|
// excelize.Cell{Value: 1},
|
|
|
|
// excelize.Cell{Value: 2},
|
|
|
|
// excelize.Cell{Formula: "SUM(A1,B1)"}});
|
2021-03-07 15:02:04 +08:00
|
|
|
//
|
2021-09-29 23:08:17 +08:00
|
|
|
// Set cell value and rows style for a worksheet with stream writer:
|
|
|
|
//
|
2022-08-13 11:21:59 +08:00
|
|
|
// err := streamWriter.SetRow("A1", []interface{}{
|
|
|
|
// excelize.Cell{Value: 1}},
|
|
|
|
// excelize.RowOpts{StyleID: styleID, Height: 20, Hidden: false});
|
2019-12-10 00:16:17 +08:00
|
|
|
func (f *File) NewStreamWriter(sheet string) (*StreamWriter, error) {
|
2020-04-23 02:01:14 +08:00
|
|
|
sheetID := f.getSheetID(sheet)
|
2020-07-11 02:31:02 +08:00
|
|
|
if sheetID == -1 {
|
2022-08-19 23:24:13 +08:00
|
|
|
return nil, newNoExistSheetError(sheet)
|
2019-12-10 00:16:17 +08:00
|
|
|
}
|
2019-12-29 12:45:10 +08:00
|
|
|
sw := &StreamWriter{
|
2022-11-16 00:02:35 +08:00
|
|
|
file: f,
|
2019-12-10 00:16:17 +08:00
|
|
|
Sheet: sheet,
|
|
|
|
SheetID: sheetID,
|
|
|
|
}
|
2020-02-13 00:00:42 +08:00
|
|
|
var err error
|
|
|
|
sw.worksheet, err = f.workSheetReader(sheet)
|
2019-12-29 12:45:10 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-10-05 22:17:11 +08:00
|
|
|
|
2022-07-18 00:21:34 +08:00
|
|
|
sheetXMLPath, _ := f.getSheetXMLPath(sheet)
|
2020-10-05 22:17:11 +08:00
|
|
|
if f.streams == nil {
|
|
|
|
f.streams = make(map[string]*StreamWriter)
|
|
|
|
}
|
2022-07-18 00:21:34 +08:00
|
|
|
f.streams[sheetXMLPath] = sw
|
2020-10-05 22:17:11 +08:00
|
|
|
|
2021-12-27 23:34:14 +08:00
|
|
|
_, _ = sw.rawData.WriteString(xml.Header + `<worksheet` + templateNamespaceIDMap)
|
2022-10-11 00:05:02 +08:00
|
|
|
bulkAppendFields(&sw.rawData, sw.worksheet, 2, 3)
|
2020-02-13 00:00:42 +08:00
|
|
|
return sw, err
|
2019-12-10 00:16:17 +08:00
|
|
|
}
|
|
|
|
|
2019-12-29 12:45:10 +08:00
|
|
|
// AddTable creates an Excel table for the StreamWriter using the given
|
2022-09-18 00:07:15 +08:00
|
|
|
// cell range and format set. For example, create a table of A1:D5:
|
2019-12-10 00:16:17 +08:00
|
|
|
//
|
2022-08-13 11:21:59 +08:00
|
|
|
// err := sw.AddTable("A1", "D5", "")
|
2019-12-10 00:16:17 +08:00
|
|
|
//
|
2019-12-29 12:45:10 +08:00
|
|
|
// Create a table of F2:H6 with format set:
|
|
|
|
//
|
2022-08-13 11:21:59 +08:00
|
|
|
// err := sw.AddTable("F2", "H6", `{
|
|
|
|
// "table_name": "table",
|
|
|
|
// "table_style": "TableStyleMedium2",
|
|
|
|
// "show_first_column": true,
|
|
|
|
// "show_last_column": true,
|
|
|
|
// "show_row_stripes": false,
|
|
|
|
// "show_column_stripes": true
|
|
|
|
// }`)
|
2019-12-29 12:45:10 +08:00
|
|
|
//
|
|
|
|
// Note that the table must be at least two lines including the header. The
|
|
|
|
// header cells must contain strings and must be unique.
|
|
|
|
//
|
2022-03-24 00:19:30 +08:00
|
|
|
// Currently, only one table is allowed for a StreamWriter. AddTable must be
|
2019-12-29 12:45:10 +08:00
|
|
|
// called after the rows are written but before Flush.
|
|
|
|
//
|
|
|
|
// See File.AddTable for details on the table format.
|
This closes #1358, made a refactor with breaking changes, see details:
This made a refactor with breaking changes:
Motivation and Context
When I decided to add set horizontal centered support for this library to resolve #1358, the reason I made this huge breaking change was:
- There are too many exported types for set sheet view, properties, and format properties, although a function using the functional options pattern can be optimized by returning an anonymous function, these types or property set or get function has no binding categorization, so I change these functions like `SetAppProps` to accept a pointer of options structure.
- Users can not easily find out which properties should be in the `SetSheetPrOptions` or `SetSheetFormatPr` categories
- Nested properties cannot proceed modify easily
Introduce 5 new export data types:
`HeaderFooterOptions`, `PageLayoutMarginsOptions`, `PageLayoutOptions`, `SheetPropsOptions`, and `ViewOptions`
Rename 4 exported data types:
- Rename `PivotTableOption` to `PivotTableOptions`
- Rename `FormatHeaderFooter` to `HeaderFooterOptions`
- Rename `FormatSheetProtection` to `SheetProtectionOptions`
- Rename `SparklineOption` to `SparklineOptions`
Remove 54 exported types:
`AutoPageBreaks`, `BaseColWidth`, `BlackAndWhite`, `CodeName`, `CustomHeight`, `Date1904`, `DefaultColWidth`, `DefaultGridColor`, `DefaultRowHeight`, `EnableFormatConditionsCalculation`, `FilterPrivacy`, `FirstPageNumber`, `FitToHeight`, `FitToPage`, `FitToWidth`, `OutlineSummaryBelow`, `PageLayoutOption`, `PageLayoutOptionPtr`, `PageLayoutOrientation`, `PageLayoutPaperSize`, `PageLayoutScale`, `PageMarginBottom`, `PageMarginFooter`, `PageMarginHeader`, `PageMarginLeft`, `PageMarginRight`, `PageMarginsOptions`, `PageMarginsOptionsPtr`, `PageMarginTop`, `Published`, `RightToLeft`, `SheetFormatPrOptions`, `SheetFormatPrOptionsPtr`, `SheetPrOption`, `SheetPrOptionPtr`, `SheetViewOption`, `SheetViewOptionPtr`, `ShowFormulas`, `ShowGridLines`, `ShowRowColHeaders`, `ShowRuler`, `ShowZeros`, `TabColorIndexed`, `TabColorRGB`, `TabColorTheme`, `TabColorTint`, `ThickBottom`, `ThickTop`, `TopLeftCell`, `View`, `WorkbookPrOption`, `WorkbookPrOptionPtr`, `ZeroHeight` and `ZoomScale`
Remove 2 exported constants:
`OrientationPortrait` and `OrientationLandscape`
Change 8 functions:
- Change the `func (f *File) SetPageLayout(sheet string, opts ...PageLayoutOption) error` to `func (f *File) SetPageLayout(sheet string, opts *PageLayoutOptions) error`
- Change the `func (f *File) GetPageLayout(sheet string, opts ...PageLayoutOptionPtr) error` to `func (f *File) GetPageLayout(sheet string) (PageLayoutOptions, error)`
- Change the `func (f *File) SetPageMargins(sheet string, opts ...PageMarginsOptions) error` to `func (f *File) SetPageMargins(sheet string, opts *PageLayoutMarginsOptions) error`
- Change the `func (f *File) GetPageMargins(sheet string, opts ...PageMarginsOptionsPtr) error` to `func (f *File) GetPageMargins(sheet string) (PageLayoutMarginsOptions, error)`
- Change the `func (f *File) SetSheetViewOptions(sheet string, viewIndex int, opts ...SheetViewOption) error` to `func (f *File) SetSheetView(sheet string, viewIndex int, opts *ViewOptions) error`
- Change the `func (f *File) GetSheetViewOptions(sheet string, viewIndex int, opts ...SheetViewOptionPtr) error` to `func (f *File) GetSheetView(sheet string, viewIndex int) (ViewOptions, error)`
- Change the `func (f *File) SetWorkbookPrOptions(opts ...WorkbookPrOption) error` to `func (f *File) SetWorkbookProps(opts *WorkbookPropsOptions) error`
- Change the `func (f *File) GetWorkbookPrOptions(opts ...WorkbookPrOptionPtr) error` to `func (f *File) GetWorkbookProps() (WorkbookPropsOptions, error)`
Introduce new function to instead of existing functions:
- New function `func (f *File) SetSheetProps(sheet string, opts *SheetPropsOptions) error` instead of `func (f *File) SetSheetPrOptions(sheet string, opts ...SheetPrOption) error` and `func (f *File) SetSheetFormatPr(sheet string, opts ...SheetFormatPrOption
2022-09-29 22:00:21 +08:00
|
|
|
func (sw *StreamWriter) AddTable(hCell, vCell, opts string) error {
|
|
|
|
options, err := parseTableOptions(opts)
|
2019-12-10 00:16:17 +08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-12-29 12:45:10 +08:00
|
|
|
|
2022-09-28 00:04:17 +08:00
|
|
|
coordinates, err := cellRefsToCoordinates(hCell, vCell)
|
2019-12-29 12:45:10 +08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
2019-12-10 00:16:17 +08:00
|
|
|
}
|
2019-12-29 16:02:31 +08:00
|
|
|
_ = sortCoordinates(coordinates)
|
2019-12-29 12:45:10 +08:00
|
|
|
|
|
|
|
// Correct the minimum number of rows, the table at least two lines.
|
|
|
|
if coordinates[1] == coordinates[3] {
|
|
|
|
coordinates[3]++
|
|
|
|
}
|
|
|
|
|
2022-09-18 00:07:15 +08:00
|
|
|
// Correct table reference range, such correct C1:B3 to B1:C3.
|
2022-11-16 00:02:35 +08:00
|
|
|
ref, err := sw.file.coordinatesToRangeRef(coordinates)
|
2019-12-29 12:45:10 +08:00
|
|
|
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,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-16 00:02:35 +08:00
|
|
|
tableID := sw.file.countTables() + 1
|
2019-12-29 12:45:10 +08:00
|
|
|
|
This closes #1358, made a refactor with breaking changes, see details:
This made a refactor with breaking changes:
Motivation and Context
When I decided to add set horizontal centered support for this library to resolve #1358, the reason I made this huge breaking change was:
- There are too many exported types for set sheet view, properties, and format properties, although a function using the functional options pattern can be optimized by returning an anonymous function, these types or property set or get function has no binding categorization, so I change these functions like `SetAppProps` to accept a pointer of options structure.
- Users can not easily find out which properties should be in the `SetSheetPrOptions` or `SetSheetFormatPr` categories
- Nested properties cannot proceed modify easily
Introduce 5 new export data types:
`HeaderFooterOptions`, `PageLayoutMarginsOptions`, `PageLayoutOptions`, `SheetPropsOptions`, and `ViewOptions`
Rename 4 exported data types:
- Rename `PivotTableOption` to `PivotTableOptions`
- Rename `FormatHeaderFooter` to `HeaderFooterOptions`
- Rename `FormatSheetProtection` to `SheetProtectionOptions`
- Rename `SparklineOption` to `SparklineOptions`
Remove 54 exported types:
`AutoPageBreaks`, `BaseColWidth`, `BlackAndWhite`, `CodeName`, `CustomHeight`, `Date1904`, `DefaultColWidth`, `DefaultGridColor`, `DefaultRowHeight`, `EnableFormatConditionsCalculation`, `FilterPrivacy`, `FirstPageNumber`, `FitToHeight`, `FitToPage`, `FitToWidth`, `OutlineSummaryBelow`, `PageLayoutOption`, `PageLayoutOptionPtr`, `PageLayoutOrientation`, `PageLayoutPaperSize`, `PageLayoutScale`, `PageMarginBottom`, `PageMarginFooter`, `PageMarginHeader`, `PageMarginLeft`, `PageMarginRight`, `PageMarginsOptions`, `PageMarginsOptionsPtr`, `PageMarginTop`, `Published`, `RightToLeft`, `SheetFormatPrOptions`, `SheetFormatPrOptionsPtr`, `SheetPrOption`, `SheetPrOptionPtr`, `SheetViewOption`, `SheetViewOptionPtr`, `ShowFormulas`, `ShowGridLines`, `ShowRowColHeaders`, `ShowRuler`, `ShowZeros`, `TabColorIndexed`, `TabColorRGB`, `TabColorTheme`, `TabColorTint`, `ThickBottom`, `ThickTop`, `TopLeftCell`, `View`, `WorkbookPrOption`, `WorkbookPrOptionPtr`, `ZeroHeight` and `ZoomScale`
Remove 2 exported constants:
`OrientationPortrait` and `OrientationLandscape`
Change 8 functions:
- Change the `func (f *File) SetPageLayout(sheet string, opts ...PageLayoutOption) error` to `func (f *File) SetPageLayout(sheet string, opts *PageLayoutOptions) error`
- Change the `func (f *File) GetPageLayout(sheet string, opts ...PageLayoutOptionPtr) error` to `func (f *File) GetPageLayout(sheet string) (PageLayoutOptions, error)`
- Change the `func (f *File) SetPageMargins(sheet string, opts ...PageMarginsOptions) error` to `func (f *File) SetPageMargins(sheet string, opts *PageLayoutMarginsOptions) error`
- Change the `func (f *File) GetPageMargins(sheet string, opts ...PageMarginsOptionsPtr) error` to `func (f *File) GetPageMargins(sheet string) (PageLayoutMarginsOptions, error)`
- Change the `func (f *File) SetSheetViewOptions(sheet string, viewIndex int, opts ...SheetViewOption) error` to `func (f *File) SetSheetView(sheet string, viewIndex int, opts *ViewOptions) error`
- Change the `func (f *File) GetSheetViewOptions(sheet string, viewIndex int, opts ...SheetViewOptionPtr) error` to `func (f *File) GetSheetView(sheet string, viewIndex int) (ViewOptions, error)`
- Change the `func (f *File) SetWorkbookPrOptions(opts ...WorkbookPrOption) error` to `func (f *File) SetWorkbookProps(opts *WorkbookPropsOptions) error`
- Change the `func (f *File) GetWorkbookPrOptions(opts ...WorkbookPrOptionPtr) error` to `func (f *File) GetWorkbookProps() (WorkbookPropsOptions, error)`
Introduce new function to instead of existing functions:
- New function `func (f *File) SetSheetProps(sheet string, opts *SheetPropsOptions) error` instead of `func (f *File) SetSheetPrOptions(sheet string, opts ...SheetPrOption) error` and `func (f *File) SetSheetFormatPr(sheet string, opts ...SheetFormatPrOption
2022-09-29 22:00:21 +08:00
|
|
|
name := options.TableName
|
2019-12-29 12:45:10 +08:00
|
|
|
if name == "" {
|
|
|
|
name = "Table" + strconv.Itoa(tableID)
|
|
|
|
}
|
|
|
|
|
|
|
|
table := xlsxTable{
|
2020-07-18 15:15:16 +08:00
|
|
|
XMLNS: NameSpaceSpreadSheet.Value,
|
2019-12-29 12:45:10 +08:00
|
|
|
ID: tableID,
|
|
|
|
Name: name,
|
|
|
|
DisplayName: name,
|
|
|
|
Ref: ref,
|
|
|
|
AutoFilter: &xlsxAutoFilter{
|
|
|
|
Ref: ref,
|
|
|
|
},
|
|
|
|
TableColumns: &xlsxTableColumns{
|
|
|
|
Count: len(tableColumn),
|
|
|
|
TableColumn: tableColumn,
|
|
|
|
},
|
|
|
|
TableStyleInfo: &xlsxTableStyleInfo{
|
This closes #1358, made a refactor with breaking changes, see details:
This made a refactor with breaking changes:
Motivation and Context
When I decided to add set horizontal centered support for this library to resolve #1358, the reason I made this huge breaking change was:
- There are too many exported types for set sheet view, properties, and format properties, although a function using the functional options pattern can be optimized by returning an anonymous function, these types or property set or get function has no binding categorization, so I change these functions like `SetAppProps` to accept a pointer of options structure.
- Users can not easily find out which properties should be in the `SetSheetPrOptions` or `SetSheetFormatPr` categories
- Nested properties cannot proceed modify easily
Introduce 5 new export data types:
`HeaderFooterOptions`, `PageLayoutMarginsOptions`, `PageLayoutOptions`, `SheetPropsOptions`, and `ViewOptions`
Rename 4 exported data types:
- Rename `PivotTableOption` to `PivotTableOptions`
- Rename `FormatHeaderFooter` to `HeaderFooterOptions`
- Rename `FormatSheetProtection` to `SheetProtectionOptions`
- Rename `SparklineOption` to `SparklineOptions`
Remove 54 exported types:
`AutoPageBreaks`, `BaseColWidth`, `BlackAndWhite`, `CodeName`, `CustomHeight`, `Date1904`, `DefaultColWidth`, `DefaultGridColor`, `DefaultRowHeight`, `EnableFormatConditionsCalculation`, `FilterPrivacy`, `FirstPageNumber`, `FitToHeight`, `FitToPage`, `FitToWidth`, `OutlineSummaryBelow`, `PageLayoutOption`, `PageLayoutOptionPtr`, `PageLayoutOrientation`, `PageLayoutPaperSize`, `PageLayoutScale`, `PageMarginBottom`, `PageMarginFooter`, `PageMarginHeader`, `PageMarginLeft`, `PageMarginRight`, `PageMarginsOptions`, `PageMarginsOptionsPtr`, `PageMarginTop`, `Published`, `RightToLeft`, `SheetFormatPrOptions`, `SheetFormatPrOptionsPtr`, `SheetPrOption`, `SheetPrOptionPtr`, `SheetViewOption`, `SheetViewOptionPtr`, `ShowFormulas`, `ShowGridLines`, `ShowRowColHeaders`, `ShowRuler`, `ShowZeros`, `TabColorIndexed`, `TabColorRGB`, `TabColorTheme`, `TabColorTint`, `ThickBottom`, `ThickTop`, `TopLeftCell`, `View`, `WorkbookPrOption`, `WorkbookPrOptionPtr`, `ZeroHeight` and `ZoomScale`
Remove 2 exported constants:
`OrientationPortrait` and `OrientationLandscape`
Change 8 functions:
- Change the `func (f *File) SetPageLayout(sheet string, opts ...PageLayoutOption) error` to `func (f *File) SetPageLayout(sheet string, opts *PageLayoutOptions) error`
- Change the `func (f *File) GetPageLayout(sheet string, opts ...PageLayoutOptionPtr) error` to `func (f *File) GetPageLayout(sheet string) (PageLayoutOptions, error)`
- Change the `func (f *File) SetPageMargins(sheet string, opts ...PageMarginsOptions) error` to `func (f *File) SetPageMargins(sheet string, opts *PageLayoutMarginsOptions) error`
- Change the `func (f *File) GetPageMargins(sheet string, opts ...PageMarginsOptionsPtr) error` to `func (f *File) GetPageMargins(sheet string) (PageLayoutMarginsOptions, error)`
- Change the `func (f *File) SetSheetViewOptions(sheet string, viewIndex int, opts ...SheetViewOption) error` to `func (f *File) SetSheetView(sheet string, viewIndex int, opts *ViewOptions) error`
- Change the `func (f *File) GetSheetViewOptions(sheet string, viewIndex int, opts ...SheetViewOptionPtr) error` to `func (f *File) GetSheetView(sheet string, viewIndex int) (ViewOptions, error)`
- Change the `func (f *File) SetWorkbookPrOptions(opts ...WorkbookPrOption) error` to `func (f *File) SetWorkbookProps(opts *WorkbookPropsOptions) error`
- Change the `func (f *File) GetWorkbookPrOptions(opts ...WorkbookPrOptionPtr) error` to `func (f *File) GetWorkbookProps() (WorkbookPropsOptions, error)`
Introduce new function to instead of existing functions:
- New function `func (f *File) SetSheetProps(sheet string, opts *SheetPropsOptions) error` instead of `func (f *File) SetSheetPrOptions(sheet string, opts ...SheetPrOption) error` and `func (f *File) SetSheetFormatPr(sheet string, opts ...SheetFormatPrOption
2022-09-29 22:00:21 +08:00
|
|
|
Name: options.TableStyle,
|
|
|
|
ShowFirstColumn: options.ShowFirstColumn,
|
|
|
|
ShowLastColumn: options.ShowLastColumn,
|
|
|
|
ShowRowStripes: options.ShowRowStripes,
|
|
|
|
ShowColumnStripes: options.ShowColumnStripes,
|
2019-12-29 12:45:10 +08:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
sheetRelationshipsTableXML := "../tables/table" + strconv.Itoa(tableID) + ".xml"
|
2022-06-12 00:19:12 +08:00
|
|
|
tableXML := strings.ReplaceAll(sheetRelationshipsTableXML, "..", "xl")
|
2019-12-29 12:45:10 +08:00
|
|
|
|
|
|
|
// Add first table for given sheet.
|
2022-11-16 00:02:35 +08:00
|
|
|
sheetPath := sw.file.sheetMap[trimSheetName(sw.Sheet)]
|
2019-12-29 12:45:10 +08:00
|
|
|
sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetPath, "xl/worksheets/") + ".rels"
|
2022-11-16 00:02:35 +08:00
|
|
|
rID := sw.file.addRels(sheetRels, SourceRelationshipTable, sheetRelationshipsTableXML, "")
|
2019-12-29 12:45:10 +08:00
|
|
|
|
|
|
|
sw.tableParts = fmt.Sprintf(`<tableParts count="1"><tablePart r:id="rId%d"></tablePart></tableParts>`, rID)
|
|
|
|
|
2022-11-16 00:02:35 +08:00
|
|
|
if err = sw.file.addContentTypePart(tableID, "table"); err != nil {
|
2022-11-13 00:40:04 +08:00
|
|
|
return err
|
|
|
|
}
|
2019-12-29 12:45:10 +08:00
|
|
|
b, _ := xml.Marshal(table)
|
2022-11-16 00:02:35 +08:00
|
|
|
sw.file.saveFileList(tableXML, b)
|
2022-11-13 00:40:04 +08:00
|
|
|
return err
|
2019-12-29 12:45:10 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Extract values from a row in the StreamWriter.
|
2022-01-09 00:20:42 +08:00
|
|
|
func (sw *StreamWriter) getRowValues(hRow, hCol, vCol int) (res []string, err error) {
|
|
|
|
res = make([]string, vCol-hCol+1)
|
2019-12-29 12:45:10 +08:00
|
|
|
|
|
|
|
r, err := sw.rawData.Reader()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2022-11-16 00:02:35 +08:00
|
|
|
dec := sw.file.xmlNewDecoder(r)
|
2019-12-29 12:45:10 +08:00
|
|
|
for {
|
|
|
|
token, err := dec.Token()
|
|
|
|
if err == io.EOF {
|
|
|
|
return res, nil
|
|
|
|
}
|
2019-12-10 00:16:17 +08:00
|
|
|
if err != nil {
|
2019-12-29 12:45:10 +08:00
|
|
|
return nil, err
|
2019-12-10 00:16:17 +08:00
|
|
|
}
|
2022-01-09 00:20:42 +08:00
|
|
|
startElement, ok := getRowElement(token, hRow)
|
2019-12-29 12:45:10 +08:00
|
|
|
if !ok {
|
|
|
|
continue
|
2019-12-10 00:16:17 +08:00
|
|
|
}
|
2019-12-29 12:45:10 +08:00
|
|
|
// 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)
|
2019-12-10 00:16:17 +08:00
|
|
|
if err != nil {
|
2019-12-29 12:45:10 +08:00
|
|
|
return nil, err
|
2019-12-10 00:16:17 +08:00
|
|
|
}
|
2022-01-09 00:20:42 +08:00
|
|
|
if col < hCol || col > vCol {
|
2019-12-29 12:45:10 +08:00
|
|
|
continue
|
|
|
|
}
|
2022-11-16 00:02:35 +08:00
|
|
|
res[col-hCol], _ = c.getValueFrom(sw.file, nil, false)
|
2019-12-10 00:16:17 +08:00
|
|
|
}
|
2019-12-29 12:45:10 +08:00
|
|
|
return res, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if the token is an XLSX row with the matching row number.
|
2022-01-09 00:20:42 +08:00
|
|
|
func getRowElement(token xml.Token, hRow int) (startElement xml.StartElement, ok bool) {
|
2019-12-29 12:45:10 +08:00
|
|
|
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)
|
2022-01-09 00:20:42 +08:00
|
|
|
if row == hRow {
|
2019-12-29 12:45:10 +08:00
|
|
|
ok = true
|
|
|
|
return
|
2019-12-10 00:16:17 +08:00
|
|
|
}
|
|
|
|
}
|
2019-12-29 12:45:10 +08:00
|
|
|
return
|
2019-12-10 00:16:17 +08:00
|
|
|
}
|
|
|
|
|
2019-12-29 12:45:10 +08:00
|
|
|
// Cell can be used directly in StreamWriter.SetRow to specify a style and
|
|
|
|
// a value.
|
|
|
|
type Cell struct {
|
|
|
|
StyleID int
|
2021-03-07 15:02:04 +08:00
|
|
|
Formula string
|
2019-12-29 16:02:31 +08:00
|
|
|
Value interface{}
|
2019-12-29 12:45:10 +08:00
|
|
|
}
|
2019-12-10 00:16:17 +08:00
|
|
|
|
2021-09-29 23:08:17 +08:00
|
|
|
// 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.
|
2021-07-25 00:43:07 +08:00
|
|
|
type RowOpts struct {
|
2022-12-19 09:28:43 +08:00
|
|
|
Height float64
|
|
|
|
Hidden bool
|
|
|
|
StyleID int
|
|
|
|
OutlineLevel int
|
2021-07-25 00:43:07 +08:00
|
|
|
}
|
|
|
|
|
2022-09-21 00:29:34 +08:00
|
|
|
// marshalAttrs prepare attributes of the row.
|
2022-10-13 00:02:53 +08:00
|
|
|
func (r *RowOpts) marshalAttrs() (strings.Builder, error) {
|
|
|
|
var (
|
|
|
|
err error
|
|
|
|
attrs strings.Builder
|
|
|
|
)
|
2022-09-21 00:29:34 +08:00
|
|
|
if r == nil {
|
2022-10-13 00:02:53 +08:00
|
|
|
return attrs, err
|
2022-09-21 00:29:34 +08:00
|
|
|
}
|
|
|
|
if r.Height > MaxRowHeight {
|
|
|
|
err = ErrMaxRowHeight
|
2022-10-13 00:02:53 +08:00
|
|
|
return attrs, err
|
2022-09-21 00:29:34 +08:00
|
|
|
}
|
2022-12-19 09:28:43 +08:00
|
|
|
if r.OutlineLevel > 7 {
|
|
|
|
err = ErrOutlineLevel
|
|
|
|
return attrs, err
|
|
|
|
}
|
2022-09-21 00:29:34 +08:00
|
|
|
if r.StyleID > 0 {
|
2022-10-13 00:02:53 +08:00
|
|
|
attrs.WriteString(` s="`)
|
|
|
|
attrs.WriteString(strconv.Itoa(r.StyleID))
|
|
|
|
attrs.WriteString(`" customFormat="1"`)
|
2022-09-21 00:29:34 +08:00
|
|
|
}
|
|
|
|
if r.Height > 0 {
|
2022-10-13 00:02:53 +08:00
|
|
|
attrs.WriteString(` ht="`)
|
|
|
|
attrs.WriteString(strconv.FormatFloat(r.Height, 'f', -1, 64))
|
|
|
|
attrs.WriteString(`" customHeight="1"`)
|
2022-09-21 00:29:34 +08:00
|
|
|
}
|
2022-12-19 09:28:43 +08:00
|
|
|
if r.OutlineLevel > 0 {
|
|
|
|
attrs.WriteString(` outlineLevel="`)
|
|
|
|
attrs.WriteString(strconv.Itoa(r.OutlineLevel))
|
|
|
|
attrs.WriteString(`"`)
|
|
|
|
}
|
2022-09-21 00:29:34 +08:00
|
|
|
if r.Hidden {
|
2022-10-13 00:02:53 +08:00
|
|
|
attrs.WriteString(` hidden="1"`)
|
2022-09-21 00:29:34 +08:00
|
|
|
}
|
2022-10-13 00:02:53 +08:00
|
|
|
return attrs, err
|
2022-09-21 00:29:34 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2022-11-29 00:03:49 +08:00
|
|
|
// 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.
|
2019-12-29 12:45:10 +08:00
|
|
|
//
|
|
|
|
// As a special case, if Cell is used as a value, then the Cell.StyleID will be
|
|
|
|
// applied to that cell.
|
2022-09-18 00:07:15 +08:00
|
|
|
func (sw *StreamWriter) SetRow(cell string, values []interface{}, opts ...RowOpts) error {
|
|
|
|
col, row, err := CellNameToCoordinates(cell)
|
2019-12-10 00:16:17 +08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-10-20 00:02:30 +08:00
|
|
|
if row <= sw.rows {
|
|
|
|
return newStreamSetRowError(row)
|
|
|
|
}
|
|
|
|
sw.rows = row
|
2022-10-11 00:05:02 +08:00
|
|
|
sw.writeSheetData()
|
2022-09-21 00:29:34 +08:00
|
|
|
options := parseRowOpts(opts...)
|
|
|
|
attrs, err := options.marshalAttrs()
|
2021-07-25 00:43:07 +08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-10-24 00:02:22 +08:00
|
|
|
_, _ = sw.rawData.WriteString(`<row r="`)
|
|
|
|
_, _ = sw.rawData.WriteString(strconv.Itoa(row))
|
|
|
|
_, _ = sw.rawData.WriteString(`"`)
|
|
|
|
_, _ = sw.rawData.WriteString(attrs.String())
|
|
|
|
_, _ = sw.rawData.WriteString(`>`)
|
2019-12-29 12:45:10 +08:00
|
|
|
for i, val := range values {
|
2022-08-04 16:50:33 +08:00
|
|
|
if val == nil {
|
|
|
|
continue
|
|
|
|
}
|
2022-09-18 00:07:15 +08:00
|
|
|
ref, err := CoordinatesToCellName(col+i, row)
|
2019-12-10 00:16:17 +08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-09-21 00:29:34 +08:00
|
|
|
c := xlsxC{R: ref, S: options.StyleID}
|
2019-12-29 12:45:10 +08:00
|
|
|
if v, ok := val.(Cell); ok {
|
|
|
|
c.S = v.StyleID
|
|
|
|
val = v.Value
|
2021-03-07 15:02:04 +08:00
|
|
|
setCellFormula(&c, v.Formula)
|
2019-12-29 12:45:10 +08:00
|
|
|
} else if v, ok := val.(*Cell); ok && v != nil {
|
|
|
|
c.S = v.StyleID
|
|
|
|
val = v.Value
|
2021-03-07 15:02:04 +08:00
|
|
|
setCellFormula(&c, v.Formula)
|
2019-12-10 00:16:17 +08:00
|
|
|
}
|
2021-12-31 00:00:01 +08:00
|
|
|
if err = sw.setCellValFunc(&c, val); err != nil {
|
2021-02-15 00:09:35 +08:00
|
|
|
_, _ = sw.rawData.WriteString(`</row>`)
|
2019-12-10 00:16:17 +08:00
|
|
|
return err
|
|
|
|
}
|
2019-12-29 12:45:10 +08:00
|
|
|
writeCell(&sw.rawData, c)
|
2019-12-10 00:16:17 +08:00
|
|
|
}
|
2021-02-15 00:09:35 +08:00
|
|
|
_, _ = sw.rawData.WriteString(`</row>`)
|
2019-12-29 12:45:10 +08:00
|
|
|
return sw.rawData.Sync()
|
|
|
|
}
|
2019-12-10 00:16:17 +08:00
|
|
|
|
2021-05-10 00:09:24 +08:00
|
|
|
// SetColWidth provides a function to set the width of a single column or
|
2021-06-05 00:06:14 +08:00
|
|
|
// multiple columns for the StreamWriter. Note that you must call
|
2021-05-10 00:09:24 +08:00
|
|
|
// the 'SetColWidth' function before the 'SetRow' function. For example set
|
|
|
|
// the width column B:C as 20:
|
|
|
|
//
|
2022-08-13 11:21:59 +08:00
|
|
|
// err := streamWriter.SetColWidth(2, 3, 20)
|
2021-05-10 00:09:24 +08:00
|
|
|
func (sw *StreamWriter) SetColWidth(min, max int, width float64) error {
|
|
|
|
if sw.sheetWritten {
|
|
|
|
return ErrStreamSetColWidth
|
|
|
|
}
|
2022-07-14 23:36:43 +08:00
|
|
|
if min < MinColumns || min > MaxColumns || max < MinColumns || max > MaxColumns {
|
2021-05-10 00:09:24 +08:00
|
|
|
return ErrColumnNumber
|
|
|
|
}
|
|
|
|
if width > MaxColumnWidth {
|
|
|
|
return ErrColumnWidth
|
|
|
|
}
|
|
|
|
if min > max {
|
|
|
|
min, max = max, min
|
|
|
|
}
|
2022-10-13 00:02:53 +08:00
|
|
|
|
|
|
|
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"/>`)
|
2021-05-10 00:09:24 +08:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-11-29 00:03:49 +08:00
|
|
|
// 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.
|
2022-11-16 00:02:35 +08:00
|
|
|
func (sw *StreamWriter) InsertPageBreak(cell string) error {
|
|
|
|
return sw.worksheet.insertPageBreak(cell)
|
|
|
|
}
|
|
|
|
|
2022-10-11 00:05:02 +08:00
|
|
|
// SetPanes provides a function to create and remove freeze panes and split
|
2022-11-29 00:03:49 +08:00
|
|
|
// panes by giving panes options for the StreamWriter. Note that you must call
|
|
|
|
// the 'SetPanes' function before the 'SetRow' function.
|
2022-10-11 00:05:02 +08:00
|
|
|
func (sw *StreamWriter) SetPanes(panes string) error {
|
|
|
|
if sw.sheetWritten {
|
|
|
|
return ErrStreamSetPanes
|
|
|
|
}
|
|
|
|
return sw.worksheet.setPanes(panes)
|
|
|
|
}
|
|
|
|
|
2022-09-18 00:07:15 +08:00
|
|
|
// MergeCell provides a function to merge cells by a given range reference for
|
2021-04-28 10:04:36 +08:00
|
|
|
// the StreamWriter. Don't create a merged cell that overlaps with another
|
|
|
|
// existing merged cell.
|
2022-01-09 00:20:42 +08:00
|
|
|
func (sw *StreamWriter) MergeCell(hCell, vCell string) error {
|
2022-09-28 00:04:17 +08:00
|
|
|
_, err := cellRefsToCoordinates(hCell, vCell)
|
2021-04-28 10:04:36 +08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
sw.mergeCellsCount++
|
2022-09-23 00:02:45 +08:00
|
|
|
_, _ = sw.mergeCells.WriteString(`<mergeCell ref="`)
|
|
|
|
_, _ = sw.mergeCells.WriteString(hCell)
|
|
|
|
_, _ = sw.mergeCells.WriteString(`:`)
|
|
|
|
_, _ = sw.mergeCells.WriteString(vCell)
|
|
|
|
_, _ = sw.mergeCells.WriteString(`"/>`)
|
2021-04-28 10:04:36 +08:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-03-07 15:02:04 +08:00
|
|
|
// setCellFormula provides a function to set formula of a cell.
|
|
|
|
func setCellFormula(c *xlsxC, formula string) {
|
|
|
|
if formula != "" {
|
2022-10-25 10:24:45 +08:00
|
|
|
c.T, c.F = "str", &xlsxF{Content: formula}
|
2021-03-07 15:02:04 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-13 00:40:04 +08:00
|
|
|
// 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
|
2022-11-16 00:02:35 +08:00
|
|
|
wb, err := sw.file.workbookReader()
|
2022-11-13 00:40:04 +08:00
|
|
|
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 {
|
2022-11-16 00:02:35 +08:00
|
|
|
style, _ := sw.file.NewStyle(&Style{NumFmt: 22})
|
2022-11-13 00:40:04 +08:00
|
|
|
c.S = style
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-12-29 16:02:31 +08:00
|
|
|
// setCellValFunc provides a function to set value of a cell.
|
2022-10-10 00:11:18 +08:00
|
|
|
func (sw *StreamWriter) setCellValFunc(c *xlsxC, val interface{}) error {
|
|
|
|
var err error
|
2019-12-29 16:02:31 +08:00
|
|
|
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:
|
2022-10-25 10:24:45 +08:00
|
|
|
c.setCellValue(val)
|
2019-12-29 16:02:31 +08:00
|
|
|
case []byte:
|
2022-10-25 10:24:45 +08:00
|
|
|
c.setCellValue(string(val))
|
2019-12-29 16:02:31 +08:00
|
|
|
case time.Duration:
|
|
|
|
c.T, c.V = setCellDuration(val)
|
|
|
|
case time.Time:
|
2022-11-13 00:40:04 +08:00
|
|
|
err = sw.setCellTime(c, val)
|
2019-12-29 16:02:31 +08:00
|
|
|
case bool:
|
|
|
|
c.T, c.V = setCellBool(val)
|
|
|
|
case nil:
|
2022-10-25 10:24:45 +08:00
|
|
|
c.setCellValue("")
|
2022-10-10 00:11:18 +08:00
|
|
|
case []RichTextRun:
|
|
|
|
c.T, c.IS = "inlineStr", &xlsxSI{}
|
|
|
|
c.IS.R, err = setRichText(val)
|
2019-12-29 16:02:31 +08:00
|
|
|
default:
|
2022-10-25 10:24:45 +08:00
|
|
|
c.setCellValue(fmt.Sprint(val))
|
2019-12-29 16:02:31 +08:00
|
|
|
}
|
|
|
|
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:
|
|
|
|
c.T, c.V = setCellInt(int(val))
|
|
|
|
case uint8:
|
|
|
|
c.T, c.V = setCellInt(int(val))
|
|
|
|
case uint16:
|
|
|
|
c.T, c.V = setCellInt(int(val))
|
|
|
|
case uint32:
|
|
|
|
c.T, c.V = setCellInt(int(val))
|
|
|
|
case uint64:
|
|
|
|
c.T, c.V = setCellInt(int(val))
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-10-11 00:05:02 +08:00
|
|
|
// writeCell constructs a cell XML and writes it to the buffer.
|
2019-12-29 12:45:10 +08:00
|
|
|
func writeCell(buf *bufferedWriter, c xlsxC) {
|
2021-02-15 00:09:35 +08:00
|
|
|
_, _ = buf.WriteString(`<c`)
|
2019-12-29 12:45:10 +08:00
|
|
|
if c.XMLSpace.Value != "" {
|
2022-10-13 00:02:53 +08:00
|
|
|
_, _ = 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(`"`)
|
2019-12-29 12:45:10 +08:00
|
|
|
if c.S != 0 {
|
2022-10-13 00:02:53 +08:00
|
|
|
_, _ = buf.WriteString(` s="`)
|
|
|
|
_, _ = buf.WriteString(strconv.Itoa(c.S))
|
|
|
|
_, _ = buf.WriteString(`"`)
|
2019-12-29 12:45:10 +08:00
|
|
|
}
|
|
|
|
if c.T != "" {
|
2022-10-13 00:02:53 +08:00
|
|
|
_, _ = buf.WriteString(` t="`)
|
|
|
|
_, _ = buf.WriteString(c.T)
|
|
|
|
_, _ = buf.WriteString(`"`)
|
2019-12-29 12:45:10 +08:00
|
|
|
}
|
2021-02-15 00:09:35 +08:00
|
|
|
_, _ = buf.WriteString(`>`)
|
2021-03-07 15:02:04 +08:00
|
|
|
if c.F != nil {
|
|
|
|
_, _ = buf.WriteString(`<f>`)
|
|
|
|
_ = xml.EscapeText(buf, []byte(c.F.Content))
|
|
|
|
_, _ = buf.WriteString(`</f>`)
|
|
|
|
}
|
2019-12-29 12:45:10 +08:00
|
|
|
if c.V != "" {
|
2021-02-15 00:09:35 +08:00
|
|
|
_, _ = buf.WriteString(`<v>`)
|
|
|
|
_ = xml.EscapeText(buf, []byte(c.V))
|
|
|
|
_, _ = buf.WriteString(`</v>`)
|
2019-12-29 12:45:10 +08:00
|
|
|
}
|
2022-10-10 00:11:18 +08:00
|
|
|
if c.IS != nil {
|
2022-10-25 10:24:45 +08:00
|
|
|
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>`)
|
|
|
|
}
|
2022-10-10 00:11:18 +08:00
|
|
|
}
|
2021-02-15 00:09:35 +08:00
|
|
|
_, _ = buf.WriteString(`</c>`)
|
2019-12-10 00:16:17 +08:00
|
|
|
}
|
|
|
|
|
2022-10-11 00:05:02 +08:00
|
|
|
// writeSheetData prepares the element preceding sheetData and writes the
|
|
|
|
// sheetData XML start element to the buffer.
|
|
|
|
func (sw *StreamWriter) writeSheetData() {
|
2021-05-14 00:09:23 +08:00
|
|
|
if !sw.sheetWritten {
|
2022-10-11 00:05:02 +08:00
|
|
|
bulkAppendFields(&sw.rawData, sw.worksheet, 4, 5)
|
2022-10-13 00:02:53 +08:00
|
|
|
if sw.cols.Len() > 0 {
|
|
|
|
_, _ = sw.rawData.WriteString("<cols>")
|
|
|
|
_, _ = sw.rawData.WriteString(sw.cols.String())
|
|
|
|
_, _ = sw.rawData.WriteString("</cols>")
|
2022-10-11 00:05:02 +08:00
|
|
|
}
|
2021-05-14 00:09:23 +08:00
|
|
|
_, _ = sw.rawData.WriteString(`<sheetData>`)
|
|
|
|
sw.sheetWritten = true
|
|
|
|
}
|
2022-10-11 00:05:02 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Flush ending the streaming writing process.
|
|
|
|
func (sw *StreamWriter) Flush() error {
|
|
|
|
sw.writeSheetData()
|
2021-02-15 00:09:35 +08:00
|
|
|
_, _ = sw.rawData.WriteString(`</sheetData>`)
|
2021-04-28 10:04:36 +08:00
|
|
|
bulkAppendFields(&sw.rawData, sw.worksheet, 8, 15)
|
2022-09-23 00:02:45 +08:00
|
|
|
mergeCells := strings.Builder{}
|
2021-04-28 10:04:36 +08:00
|
|
|
if sw.mergeCellsCount > 0 {
|
2022-09-23 00:02:45 +08:00
|
|
|
_, _ = mergeCells.WriteString(`<mergeCells count="`)
|
|
|
|
_, _ = mergeCells.WriteString(strconv.Itoa(sw.mergeCellsCount))
|
|
|
|
_, _ = mergeCells.WriteString(`">`)
|
|
|
|
_, _ = mergeCells.WriteString(sw.mergeCells.String())
|
|
|
|
_, _ = mergeCells.WriteString(`</mergeCells>`)
|
2021-04-28 10:04:36 +08:00
|
|
|
}
|
2022-09-23 00:02:45 +08:00
|
|
|
_, _ = sw.rawData.WriteString(mergeCells.String())
|
2021-04-28 10:04:36 +08:00
|
|
|
bulkAppendFields(&sw.rawData, sw.worksheet, 17, 38)
|
2021-02-15 00:09:35 +08:00
|
|
|
_, _ = sw.rawData.WriteString(sw.tableParts)
|
2020-08-15 18:41:45 +08:00
|
|
|
bulkAppendFields(&sw.rawData, sw.worksheet, 40, 40)
|
2021-02-15 00:09:35 +08:00
|
|
|
_, _ = sw.rawData.WriteString(`</worksheet>`)
|
2019-12-29 12:45:10 +08:00
|
|
|
if err := sw.rawData.Flush(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-11-16 00:02:35 +08:00
|
|
|
sheetPath := sw.file.sheetMap[trimSheetName(sw.Sheet)]
|
|
|
|
sw.file.Sheet.Delete(sheetPath)
|
|
|
|
delete(sw.file.checked, sheetPath)
|
|
|
|
sw.file.Pkg.Delete(sheetPath)
|
2019-12-29 12:45:10 +08:00
|
|
|
|
|
|
|
return nil
|
2019-12-10 00:16:17 +08:00
|
|
|
}
|
|
|
|
|
2020-02-13 00:00:42 +08:00
|
|
|
// bulkAppendFields bulk-appends fields in a worksheet by specified field
|
|
|
|
// names order range.
|
|
|
|
func bulkAppendFields(w io.Writer, ws *xlsxWorksheet, from, to int) {
|
2019-12-10 00:16:17 +08:00
|
|
|
s := reflect.ValueOf(ws).Elem()
|
2019-12-29 12:45:10 +08:00
|
|
|
enc := xml.NewEncoder(w)
|
2019-12-10 00:16:17 +08:00
|
|
|
for i := 0; i < s.NumField(); i++ {
|
2020-02-13 00:00:42 +08:00
|
|
|
if from <= i && i <= to {
|
2021-02-15 00:09:35 +08:00
|
|
|
_ = enc.Encode(s.Field(i).Interface())
|
2019-12-10 00:16:17 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-29 12:45:10 +08:00
|
|
|
// 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
|
2019-12-29 16:02:31 +08:00
|
|
|
// is written to the temp file with Sync, which may return an error.
|
|
|
|
// Therefore, Sync should be periodically called and the error checked.
|
2019-12-29 12:45:10 +08:00
|
|
|
type bufferedWriter struct {
|
|
|
|
tmp *os.File
|
|
|
|
buf bytes.Buffer
|
|
|
|
}
|
|
|
|
|
2022-10-24 00:02:22 +08:00
|
|
|
// Write to the in-memory buffer. The error is always nil.
|
2019-12-29 12:45:10 +08:00
|
|
|
func (bw *bufferedWriter) Write(p []byte) (n int, err error) {
|
|
|
|
return bw.buf.Write(p)
|
|
|
|
}
|
|
|
|
|
2022-10-24 00:02:22 +08:00
|
|
|
// WriteString write to the in-memory buffer. The error is always nil.
|
2019-12-29 12:45:10 +08:00
|
|
|
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
|
2019-12-10 00:16:17 +08:00
|
|
|
}
|
2019-12-29 12:45:10 +08:00
|
|
|
// os.File.ReadAt does not affect the cursor position and is safe to use here
|
|
|
|
return io.NewSectionReader(bw.tmp, 0, fi.Size()), nil
|
|
|
|
}
|
|
|
|
|
2019-12-29 16:02:31 +08:00
|
|
|
// 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.
|
2019-12-29 12:45:10 +08:00
|
|
|
func (bw *bufferedWriter) Sync() (err error) {
|
|
|
|
// Try to use local storage
|
2021-04-30 00:14:42 +08:00
|
|
|
if bw.buf.Len() < StreamChunkSize {
|
2019-12-29 12:45:10 +08:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if bw.tmp == nil {
|
2022-10-13 00:02:53 +08:00
|
|
|
bw.tmp, err = os.CreateTemp(os.TempDir(), "excelize-")
|
2019-12-29 12:45:10 +08:00
|
|
|
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
|
2019-12-10 00:16:17 +08:00
|
|
|
}
|
2019-12-29 12:45:10 +08:00
|
|
|
defer os.Remove(bw.tmp.Name())
|
|
|
|
return bw.tmp.Close()
|
2019-12-10 00:16:17 +08:00
|
|
|
}
|