1
0
mirror of https://github.com/pbnjay/grate.git synced 2024-12-13 22:04:59 +02:00
grate/xls/sheets.go

276 lines
7.1 KiB
Go
Raw Normal View History

2021-02-08 05:51:20 +02:00
package xls
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"log"
"math"
"time"
"unicode/utf16"
)
func (b *WorkBook) Sheets() []string {
res := make([]string, len(b.sheets))
for i, s := range b.sheets {
res[i] = s.Name
}
return res
}
func (b *WorkBook) Get(sheetName string) (*WorkSheet, error) {
for _, s := range b.sheets {
if s.Name == sheetName {
ss := b.pos2substream[int64(s.Position)]
ws := &WorkSheet{
b: b, s: s, ss: ss,
}
return ws, ws.parse()
}
}
return nil, errors.New("xls: sheet not found")
}
type WorkSheet struct {
b *WorkBook
s *boundSheet
ss int
err error
rows []*row
maxcol int
iterRow int
}
type staticBlankType byte
const staticBlank staticBlankType = 0
func (staticBlankType) String() string {
return ""
}
type row struct {
// each value must be one of: int, float64, string, or time.Time
cols []interface{}
}
func (s *WorkSheet) placeValue(rowIndex, colIndex int, val interface{}) {
// ensure we always have a complete matrix
if s.maxcol <= colIndex {
s.maxcol = colIndex + 1
for _, r := range s.rows {
for len(r.cols) <= colIndex {
r.cols = append(r.cols, staticBlank)
}
}
}
for len(s.rows) <= rowIndex {
emptyRow := make([]interface{}, s.maxcol)
for i := 0; i < s.maxcol; i++ {
emptyRow[i] = staticBlank
}
s.rows = append(s.rows, &row{emptyRow})
}
s.rows[rowIndex].cols[colIndex] = val
}
func (s *WorkSheet) parse() error {
for _, r := range s.b.substreams[s.ss] {
bb := bytes.NewReader(r.Data)
switch r.RecType {
case RecTypeWindow2:
opts := binary.LittleEndian.Uint16(r.Data)
// right-to-left = 0x40, selected = 0x400
log.Printf("sheet options: %x", opts)
case RecTypeRow:
row := &shRow{}
binary.Read(bb, binary.LittleEndian, row)
log.Printf("row spec: %+v", *row)
case RecTypeBlank:
var rowIndex, colIndex uint16
binary.Read(bb, binary.LittleEndian, &rowIndex)
binary.Read(bb, binary.LittleEndian, &colIndex)
log.Printf("blank spec: %d %d", rowIndex, colIndex)
case RecTypeMulBlank:
var rowIndex, firstCol uint16
binary.Read(bb, binary.LittleEndian, &rowIndex)
binary.Read(bb, binary.LittleEndian, &firstCol)
nrk := int((r.RecSize - 6) / 6)
log.Printf("row blanks spec: %d %d %d", rowIndex, firstCol, nrk)
case RecTypeMulRk:
mr := &shMulRK{}
nrk := int((r.RecSize - 6) / 6)
binary.Read(bb, binary.LittleEndian, &mr.RowIndex)
binary.Read(bb, binary.LittleEndian, &mr.FirstCol)
mr.Values = make([]RkRec, nrk)
for i := 0; i < nrk; i++ {
rr := RkRec{}
binary.Read(bb, binary.LittleEndian, &rr)
mr.Values[i] = rr
var rval interface{}
if rr.Value.IsInteger() {
rval = rr.Value.Int()
} else {
rval = rr.Value.Float64()
}
s.placeValue(int(mr.RowIndex), int(mr.FirstCol)+i, rval)
}
binary.Read(bb, binary.LittleEndian, &mr.LastCol)
log.Printf("mulrow spec: %+v", *mr)
case RecTypeNumber:
var rowIndex, colIndex, ixfe uint16
var xnum uint64
binary.Read(bb, binary.LittleEndian, &rowIndex)
binary.Read(bb, binary.LittleEndian, &colIndex)
binary.Read(bb, binary.LittleEndian, &ixfe)
binary.Read(bb, binary.LittleEndian, &xnum)
value := math.Float64frombits(xnum)
s.placeValue(int(rowIndex), int(colIndex), value)
log.Printf("Number spec: %d %d = %f", rowIndex, colIndex, value)
case RecTypeRK:
var rowIndex, colIndex uint16
binary.Read(bb, binary.LittleEndian, &rowIndex)
binary.Read(bb, binary.LittleEndian, &colIndex)
rr := RkRec{}
binary.Read(bb, binary.LittleEndian, &rr)
var rval interface{}
if rr.Value.IsInteger() {
rval = rr.Value.Int()
} else {
rval = rr.Value.Float64()
}
s.placeValue(int(rowIndex), int(colIndex), rval)
log.Printf("RK spec: %d %d = %s", rowIndex, colIndex, rr.Value.String())
case RecTypeFormula:
var rowIndex, colIndex uint16
binary.Read(bb, binary.LittleEndian, &rowIndex)
binary.Read(bb, binary.LittleEndian, &colIndex)
log.Printf("formula spec: %d %d ~~ %+v", rowIndex, colIndex, r.Data)
case RecTypeString:
var charCount, flags uint16
binary.Read(bb, binary.LittleEndian, &charCount)
binary.Read(bb, binary.LittleEndian, &flags)
s := ""
if (flags & 1) == 0 {
s = string(r.Data[4:])
} else {
us := make([]uint16, charCount)
binary.Read(bb, binary.LittleEndian, us)
s = string(utf16.Decode(us))
}
log.Printf("string spec: = %s", s)
case RecTypeLabelSst:
var rowIndex, colIndex, ixfe uint16
var sstIndex uint32
binary.Read(bb, binary.LittleEndian, &rowIndex)
binary.Read(bb, binary.LittleEndian, &colIndex)
binary.Read(bb, binary.LittleEndian, &ixfe)
binary.Read(bb, binary.LittleEndian, &sstIndex)
s.placeValue(int(rowIndex), int(colIndex), s.b.strings[sstIndex])
log.Printf("SST spec: %d %d = [%d] %s", rowIndex, colIndex, sstIndex, s.b.strings[sstIndex])
case RecTypeHLink:
loc := &shRef8{}
binary.Read(bb, binary.LittleEndian, loc)
var x uint64
binary.Read(bb, binary.LittleEndian, &x) // skip and discard classid
binary.Read(bb, binary.LittleEndian, &x)
var flags, slen uint32
binary.Read(bb, binary.LittleEndian, &slen)
if slen != 2 {
log.Println("unknown hyperlink version")
continue
}
str := "<hyperlink>"
binary.Read(bb, binary.LittleEndian, &flags)
if (flags & 0x10) != 0 {
binary.Read(bb, binary.LittleEndian, &slen)
us := make([]uint16, slen)
binary.Read(bb, binary.LittleEndian, us)
str = string(utf16.Decode(us))
}
// TODO: apply merge cell rules
s.placeValue(int(loc.FirstRow), int(loc.FirstCol), str)
log.Printf("hyperlink spec: %+v = %s", loc, str)
case RecTypeMergeCells:
var cmcs uint16
binary.Read(bb, binary.LittleEndian, &cmcs)
mcRefs := make([]shRef8, cmcs)
binary.Read(bb, binary.LittleEndian, &mcRefs)
log.Printf("MergeCells spec: %d records", cmcs)
for j, mc := range mcRefs {
log.Printf(" %d: %+v", j, mc)
}
default:
log.Println("worksheet", r.RecType, r.RecSize)
}
}
return nil
}
// Err returns the last error that occured.
func (s *WorkSheet) Err() error {
return s.err
}
// Next advances to the next row of content.
// It MUST be called prior to any Scan().
func (s *WorkSheet) Next() bool {
s.iterRow++
return s.iterRow < len(s.rows)
}
func (s *WorkSheet) Strings() []string {
currow := s.rows[s.iterRow]
res := make([]string, len(currow.cols))
for i, col := range currow.cols {
res[i] = fmt.Sprint(col)
}
return res
}
// Scan extracts values from the row into the provided arguments
// Arguments must be pointers to one of 4 supported types:
// int, float64, string, or time.Time
func (s *WorkSheet) Scan(args ...interface{}) error {
currow := s.rows[s.iterRow]
for i, a := range args {
switch v := a.(type) {
case *int:
*v = currow.cols[i].(int)
case *float64:
*v = currow.cols[i].(float64)
case *string:
*v = currow.cols[i].(string)
case *time.Time:
*v = currow.cols[i].(time.Time)
default:
return ErrInvalidType
}
}
return nil
}
// ErrInvalidType is returned by Scan for invalid arguments.
var ErrInvalidType = errors.New("xls: Scan only supports *int, *float64, *string, *time.Time arguments")