2021-02-21 23:25:18 -05:00
|
|
|
package commonxl
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"time"
|
2021-02-23 09:15:25 -05:00
|
|
|
|
|
|
|
"github.com/pbnjay/grate"
|
2021-02-21 23:25:18 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
// Sheet holds raw and rendered values for a spreadsheet.
|
|
|
|
type Sheet struct {
|
|
|
|
Formatter *Formatter
|
|
|
|
NumRows int
|
|
|
|
NumCols int
|
|
|
|
Rows [][]Cell
|
|
|
|
|
|
|
|
CurRow int
|
|
|
|
}
|
|
|
|
|
|
|
|
// Resize the sheet for the number of rows and cols given.
|
|
|
|
// Newly added cells default to blank.
|
|
|
|
func (s *Sheet) Resize(rows, cols int) {
|
2021-02-25 08:31:53 +01:00
|
|
|
for i := range s.Rows {
|
|
|
|
if i > rows {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
n := cols - len(s.Rows[i])
|
|
|
|
if n <= 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
s.Rows[i] = append(s.Rows[i], make([]Cell, n)...)
|
|
|
|
}
|
|
|
|
|
2021-02-21 23:25:18 -05:00
|
|
|
if rows <= 0 {
|
|
|
|
rows = 1
|
|
|
|
}
|
|
|
|
if cols <= 0 {
|
|
|
|
cols = 1
|
|
|
|
}
|
|
|
|
s.CurRow = 0
|
|
|
|
s.NumRows = rows
|
|
|
|
s.NumCols = cols
|
|
|
|
|
|
|
|
for rows >= len(s.Rows) {
|
|
|
|
s.Rows = append(s.Rows, make([]Cell, cols))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Put the value at the cell location given.
|
|
|
|
func (s *Sheet) Put(row, col int, value interface{}, fmtNum uint16) {
|
2021-02-25 08:38:22 +01:00
|
|
|
//log.Println(row, col, value, fmtNum)
|
2021-02-21 23:25:18 -05:00
|
|
|
if row >= s.NumRows || col >= s.NumCols {
|
2021-02-23 09:15:25 -05:00
|
|
|
if grate.Debug {
|
|
|
|
log.Printf("grate: cell out of bounds row %d>=%d, col %d>=%d",
|
|
|
|
row, s.NumRows, col, s.NumCols)
|
|
|
|
}
|
|
|
|
|
|
|
|
// per the spec, this is an invalid Excel file
|
|
|
|
// but we'll resize in place instead of crashing out
|
|
|
|
if row >= s.NumRows {
|
|
|
|
s.NumRows = row + 1
|
|
|
|
}
|
|
|
|
if col >= s.NumCols {
|
|
|
|
s.NumCols = col + 1
|
|
|
|
}
|
|
|
|
s.Resize(s.NumRows, s.NumCols)
|
2021-02-21 23:25:18 -05:00
|
|
|
}
|
|
|
|
|
2022-02-25 01:02:28 -05:00
|
|
|
if spec, ok := value.(string); ok {
|
|
|
|
if spec == grate.EndRowMerged || spec == grate.EndColumnMerged || spec == grate.ContinueRowMerged || spec == grate.ContinueColumnMerged {
|
|
|
|
s.Rows[row][col] = NewCell(value)
|
|
|
|
s.Rows[row][col][1] = StaticCell
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-21 23:25:18 -05:00
|
|
|
ct, ok := s.Formatter.getCellType(fmtNum)
|
|
|
|
if !ok || fmtNum == 0 {
|
|
|
|
s.Rows[row][col] = NewCell(value)
|
|
|
|
} else {
|
2021-02-22 00:01:17 -05:00
|
|
|
s.Rows[row][col] = NewCellWithType(value, ct, s.Formatter)
|
2021-02-21 23:25:18 -05:00
|
|
|
}
|
|
|
|
s.Rows[row][col].SetFormatNumber(fmtNum)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set changes the value in an existing cell location.
|
|
|
|
// NB Currently only used for populating string results for formulas.
|
|
|
|
func (s *Sheet) Set(row, col int, value interface{}) {
|
|
|
|
if row > s.NumRows || col > s.NumCols {
|
|
|
|
log.Println("grate: cell out of bounds")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
s.Rows[row][col][0] = value
|
|
|
|
s.Rows[row][col][1] = StringCell
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetURL adds a hyperlink to an existing cell location.
|
|
|
|
func (s *Sheet) SetURL(row, col int, link string) {
|
|
|
|
if row > s.NumRows || col > s.NumCols {
|
|
|
|
log.Println("grate: cell out of bounds")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
s.Rows[row][col].SetURL(link)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Next advances to the next record of content.
|
|
|
|
// It MUST be called prior to any Scan().
|
|
|
|
func (s *Sheet) Next() bool {
|
2021-02-23 08:19:15 -05:00
|
|
|
if (s.CurRow + 1) > len(s.Rows) {
|
2021-02-21 23:25:18 -05:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
s.CurRow++
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2022-02-23 00:54:59 -05:00
|
|
|
// Raw extracts the raw Cell interfaces underlying the current row.
|
|
|
|
func (s *Sheet) Raw() []Cell {
|
|
|
|
rr := make([]Cell, s.NumCols)
|
|
|
|
for i, cell := range s.Rows[s.CurRow-1] {
|
|
|
|
rr[i] = cell.Clone()
|
|
|
|
}
|
|
|
|
return rr
|
|
|
|
}
|
|
|
|
|
2021-02-21 23:25:18 -05:00
|
|
|
// Strings extracts values from the current record into a list of strings.
|
|
|
|
func (s *Sheet) Strings() []string {
|
|
|
|
res := make([]string, s.NumCols)
|
2021-02-23 08:19:15 -05:00
|
|
|
for i, cell := range s.Rows[s.CurRow-1] {
|
2021-02-21 23:25:18 -05:00
|
|
|
if cell.Type() == BlankCell {
|
|
|
|
res[i] = ""
|
|
|
|
continue
|
|
|
|
}
|
2022-02-25 01:02:28 -05:00
|
|
|
if cell.Type() == StaticCell {
|
|
|
|
res[i] = cell.Value().(string)
|
|
|
|
continue
|
|
|
|
}
|
2021-02-21 23:25:18 -05:00
|
|
|
val := cell.Value()
|
|
|
|
fs, ok := s.Formatter.Apply(cell.FormatNo(), val)
|
|
|
|
if !ok {
|
|
|
|
fs = fmt.Sprint(val)
|
|
|
|
}
|
|
|
|
res[i] = fs
|
|
|
|
}
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
2021-02-22 00:01:17 -05:00
|
|
|
// Types extracts the data types from the current record into a list.
|
|
|
|
// options: "boolean", "integer", "float", "string", "date",
|
|
|
|
// and special cases: "blank", "hyperlink" which are string types
|
|
|
|
func (s *Sheet) Types() []string {
|
|
|
|
res := make([]string, s.NumCols)
|
2021-02-23 08:19:15 -05:00
|
|
|
for i, cell := range s.Rows[s.CurRow-1] {
|
2021-02-22 00:01:17 -05:00
|
|
|
res[i] = cell.Type().String()
|
|
|
|
}
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
2021-02-23 23:29:20 -05:00
|
|
|
// Formats extracts the format code for the current record into a list.
|
|
|
|
func (s *Sheet) Formats() []string {
|
|
|
|
ok := true
|
|
|
|
res := make([]string, s.NumCols)
|
|
|
|
for i, cell := range s.Rows[s.CurRow-1] {
|
|
|
|
res[i], ok = builtInFormats[cell.FormatNo()]
|
|
|
|
if !ok {
|
|
|
|
res[i] = fmt.Sprint(cell.FormatNo())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
2021-02-21 23:25:18 -05:00
|
|
|
// Scan extracts values from the current record into the provided arguments
|
|
|
|
// Arguments must be pointers to one of 5 supported types:
|
|
|
|
// bool, int64, float64, string, or time.Time
|
|
|
|
// If invalid, returns ErrInvalidScanType
|
|
|
|
func (s *Sheet) Scan(args ...interface{}) error {
|
2021-02-23 08:19:15 -05:00
|
|
|
row := s.Rows[s.CurRow-1]
|
2021-02-21 23:25:18 -05:00
|
|
|
|
|
|
|
for i, a := range args {
|
|
|
|
val := row[i].Value()
|
|
|
|
|
|
|
|
switch v := a.(type) {
|
|
|
|
case bool, int64, float64, string, time.Time:
|
|
|
|
return fmt.Errorf("scan destinations must be pointer (arg %d is not)", i)
|
|
|
|
case *bool:
|
|
|
|
if x, ok := val.(bool); ok {
|
|
|
|
*v = x
|
|
|
|
} else {
|
|
|
|
return fmt.Errorf("scan destination %d expected *%T, not *bool", i, val)
|
|
|
|
}
|
|
|
|
case *int64:
|
|
|
|
if x, ok := val.(int64); ok {
|
|
|
|
*v = x
|
|
|
|
} else {
|
|
|
|
return fmt.Errorf("scan destination %d expected *%T, not *int64", i, val)
|
|
|
|
}
|
|
|
|
case *float64:
|
|
|
|
if x, ok := val.(float64); ok {
|
|
|
|
*v = x
|
|
|
|
} else {
|
|
|
|
return fmt.Errorf("scan destination %d expected *%T, not *float64", i, val)
|
|
|
|
}
|
|
|
|
case *string:
|
|
|
|
if x, ok := val.(string); ok {
|
|
|
|
*v = x
|
|
|
|
} else {
|
|
|
|
return fmt.Errorf("scan destination %d expected *%T, not *string", i, val)
|
|
|
|
}
|
|
|
|
case *time.Time:
|
|
|
|
if x, ok := val.(time.Time); ok {
|
|
|
|
*v = x
|
|
|
|
} else {
|
|
|
|
return fmt.Errorf("scan destination %d expected *%T, not *time.Time", i, val)
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
return fmt.Errorf("scan destination for arg %d is not supported (%T)", i, a)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsEmpty returns true if there are no data values.
|
|
|
|
func (s *Sheet) IsEmpty() bool {
|
|
|
|
return (s.NumCols <= 1 && s.NumRows <= 1)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Err returns the last error that occured.
|
|
|
|
func (s *Sheet) Err() error {
|
|
|
|
return nil
|
|
|
|
}
|