447 lines
8.6 KiB
Go
447 lines
8.6 KiB
Go
package xls
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"strconv"
|
|
"time"
|
|
)
|
|
|
|
var ErrIsInt = errors.New("is int")
|
|
|
|
/* Data types */
|
|
const TYPE_STRING2 = 1
|
|
const TYPE_STRING = 2
|
|
const TYPE_FORMULA = 3
|
|
const TYPE_NUMERIC = 4
|
|
const TYPE_BOOL = 5
|
|
const TYPE_NULL = 6
|
|
const TYPE_INLINE = 7
|
|
const TYPE_ERROR = 8
|
|
const TYPE_DATETIME = 9
|
|
const TYPE_PERCENTAGE = 10
|
|
const TYPE_CURRENCY = 11
|
|
|
|
//content type
|
|
type contentHandler interface {
|
|
Debug(wb *WorkBook)
|
|
String(*WorkBook) []string
|
|
FirstCol() uint16
|
|
LastCol() uint16
|
|
}
|
|
|
|
type Col struct {
|
|
RowB uint16
|
|
FirstColB uint16
|
|
}
|
|
|
|
type Coler interface {
|
|
Row() uint16
|
|
}
|
|
|
|
func (c *Col) Debug(wb *WorkBook) {
|
|
fmt.Printf("col dump:%#+v\n", c)
|
|
}
|
|
|
|
func (c *Col) Row() uint16 {
|
|
return c.RowB
|
|
}
|
|
|
|
func (c *Col) FirstCol() uint16 {
|
|
return c.FirstColB
|
|
}
|
|
|
|
func (c *Col) LastCol() uint16 {
|
|
return c.FirstColB
|
|
}
|
|
|
|
func (c *Col) String(wb *WorkBook) []string {
|
|
return []string{"default"}
|
|
}
|
|
|
|
type RK uint32
|
|
|
|
func (rk RK) Debug(wb *WorkBook) {
|
|
fmt.Printf("rk dump:%#+v\n", rk)
|
|
}
|
|
|
|
func (rk RK) number() (intNum int64, floatNum float64, isFloat bool) {
|
|
multiplied := rk & 1
|
|
isInt := rk & 2
|
|
val := rk >> 2
|
|
if isInt == 0 {
|
|
isFloat = true
|
|
floatNum = math.Float64frombits(uint64(val) << 34)
|
|
if multiplied != 0 {
|
|
floatNum = floatNum / 100
|
|
}
|
|
return
|
|
}
|
|
//+++ add lines from here
|
|
if multiplied != 0 {
|
|
isFloat = true
|
|
floatNum = float64(val) / 100
|
|
return
|
|
}
|
|
//+++end
|
|
return int64(val), 0, false
|
|
}
|
|
|
|
func (rk RK) float() float64 {
|
|
var i, f, isFloat = rk.number()
|
|
if !isFloat {
|
|
f = float64(i)
|
|
}
|
|
|
|
return f
|
|
}
|
|
|
|
func (rk RK) String(wb *WorkBook) string {
|
|
i, f, isFloat := rk.number()
|
|
if isFloat {
|
|
return strconv.FormatFloat(f, 'f', -1, 64)
|
|
}
|
|
|
|
return strconv.FormatInt(i, 10)
|
|
}
|
|
|
|
type XfRk struct {
|
|
Index uint16
|
|
Rk RK
|
|
}
|
|
|
|
func (xf *XfRk) Debug(wb *WorkBook) {
|
|
fmt.Printf("xfrk dump:%#+v\n", wb.Xfs[xf.Index])
|
|
xf.Rk.Debug(wb)
|
|
}
|
|
|
|
func (xf *XfRk) String(wb *WorkBook) string {
|
|
if val, ok := wb.Format(xf.Index, xf.Rk.float()); ok {
|
|
return val
|
|
}
|
|
|
|
return xf.Rk.String(wb)
|
|
}
|
|
|
|
type MulrkCol struct {
|
|
Col
|
|
Xfrks []XfRk
|
|
LastColB uint16
|
|
}
|
|
|
|
func (c *MulrkCol) Debug(wb *WorkBook) {
|
|
fmt.Printf("mulrk dump:%#+v\n", c)
|
|
|
|
for _, v := range c.Xfrks {
|
|
v.Debug(wb)
|
|
}
|
|
}
|
|
|
|
func (c *MulrkCol) LastCol() uint16 {
|
|
return c.LastColB
|
|
}
|
|
|
|
func (c *MulrkCol) String(wb *WorkBook) []string {
|
|
var res = make([]string, len(c.Xfrks))
|
|
for i, v := range c.Xfrks {
|
|
res[i] = v.String(wb)
|
|
}
|
|
|
|
return res
|
|
}
|
|
|
|
type MulBlankCol struct {
|
|
Col
|
|
Xfs []uint16
|
|
LastColB uint16
|
|
}
|
|
|
|
func (c *MulBlankCol) Debug(wb *WorkBook) {
|
|
fmt.Printf("mul blank dump:%#+v\n", c)
|
|
}
|
|
|
|
func (c *MulBlankCol) LastCol() uint16 {
|
|
return c.LastColB
|
|
}
|
|
|
|
func (c *MulBlankCol) String(wb *WorkBook) []string {
|
|
return make([]string, len(c.Xfs))
|
|
}
|
|
|
|
type NumberCol struct {
|
|
Col
|
|
Index uint16
|
|
Float float64
|
|
}
|
|
|
|
func (c *NumberCol) Debug(wb *WorkBook) {
|
|
fmt.Printf("number col dump:%#+v\n", c)
|
|
}
|
|
|
|
func (c *NumberCol) String(wb *WorkBook) []string {
|
|
if wb.Debug {
|
|
fmt.Printf("number col dump:%#+v\n", c)
|
|
}
|
|
|
|
if v, ok := wb.Format(c.Index, c.Float); ok {
|
|
return []string{v}
|
|
}
|
|
|
|
return []string{strconv.FormatFloat(c.Float, 'f', -1, 64)}
|
|
}
|
|
|
|
type FormulaColHeader struct {
|
|
Col
|
|
IndexXf uint16
|
|
Result [8]byte
|
|
Flags uint16
|
|
_ uint32
|
|
}
|
|
|
|
// Value formula header value
|
|
func (f *FormulaColHeader) Value() float64 {
|
|
var rknumhigh = ByteToUint32(f.Result[4:8])
|
|
var rknumlow = ByteToUint32(f.Result[0:4])
|
|
var sign = (rknumhigh & 0x80000000) >> 31
|
|
var exp = float64(((rknumhigh & 0x7ff00000) >> 20) - 1023)
|
|
var mantissa = (0x100000 | (rknumhigh & 0x000fffff))
|
|
var mantissalow1 = (rknumlow & 0x80000000) >> 31
|
|
var mantissalow2 = (rknumlow & 0x7fffffff)
|
|
var value = float64(mantissa) / math.Pow(2, 20-exp)
|
|
|
|
if mantissalow1 != 0 {
|
|
value += 1 / math.Pow(2, 21-exp)
|
|
}
|
|
|
|
value += float64(mantissalow2) / math.Pow(2, 52-exp)
|
|
if 0 != sign {
|
|
value *= -1
|
|
}
|
|
|
|
return value
|
|
}
|
|
|
|
// IsPart part of shared formula check
|
|
// WARNING:
|
|
// We can apparently not rely on $isPartOfSharedFormula. Even when $isPartOfSharedFormula = true
|
|
// the formula data may be ordinary formula data, therefore we need to check
|
|
// explicitly for the tExp token (0x01)
|
|
func (f *FormulaColHeader) IsPart() bool {
|
|
return 0 != (0x0008 & ByteToUint16(f.Result[6:8]))
|
|
}
|
|
|
|
type FormulaCol struct {
|
|
parsed bool
|
|
Code uint16
|
|
Btl uint16
|
|
Btc uint16
|
|
Bts []byte
|
|
Header *FormulaColHeader
|
|
ws int
|
|
vType int
|
|
value string
|
|
}
|
|
|
|
func (c *FormulaCol) Debug(wb *WorkBook) {
|
|
fmt.Printf("formula col dump:%#+v\n", c)
|
|
}
|
|
|
|
func (c *FormulaCol) Row() uint16 {
|
|
return c.Header.Col.RowB
|
|
}
|
|
|
|
func (c *FormulaCol) FirstCol() uint16 {
|
|
return c.Header.Col.FirstColB
|
|
}
|
|
|
|
func (c *FormulaCol) LastCol() uint16 {
|
|
return c.Header.Col.FirstColB
|
|
}
|
|
|
|
func (c *FormulaCol) String(wb *WorkBook) []string {
|
|
if !c.parsed {
|
|
c.parse(wb, true)
|
|
}
|
|
|
|
if wb.Debug {
|
|
fmt.Printf("formula col dump:%#+v\n", c)
|
|
}
|
|
|
|
return []string{c.value}
|
|
}
|
|
|
|
func (c *FormulaCol) parse(wb *WorkBook, ref bool) {
|
|
c.parsed = true
|
|
|
|
if 0 == c.Header.Result[0] && 255 == c.Header.Result[6] && 255 == c.Header.Result[7] {
|
|
// String formula. Result follows in appended STRING record
|
|
c.vType = TYPE_STRING
|
|
} else if 1 == c.Header.Result[0] && 255 == c.Header.Result[6] && 255 == c.Header.Result[7] {
|
|
// Boolean formula. Result is in +2; 0=false, 1=true
|
|
c.vType = TYPE_BOOL
|
|
if 0 == c.Header.Result[3] {
|
|
c.value = "false"
|
|
} else {
|
|
c.value = "true"
|
|
}
|
|
} else if 2 == c.Header.Result[0] && 255 == c.Header.Result[6] && 255 == c.Header.Result[7] {
|
|
// Error formula. Error code is in +2
|
|
c.vType = TYPE_ERROR
|
|
switch c.Header.Result[3] {
|
|
case 0x00:
|
|
c.value = "#NULL!"
|
|
case 0x07:
|
|
c.value = "#DIV/0"
|
|
case 0x0F:
|
|
c.value = "#VALUE!"
|
|
case 0x17:
|
|
c.value = "#REF!"
|
|
case 0x1D:
|
|
c.value = "#NAME?"
|
|
case 0x24:
|
|
c.value = "#NUM!"
|
|
case 0x2A:
|
|
c.value = "#N/A"
|
|
}
|
|
} else if 3 == c.Header.Result[0] && 255 == c.Header.Result[6] && 255 == c.Header.Result[7] {
|
|
// Formula result is a null string
|
|
c.vType = TYPE_NULL
|
|
c.value = ""
|
|
} else {
|
|
// formula result is a number, first 14 bytes like _NUMBER record
|
|
c.vType = TYPE_NUMERIC
|
|
|
|
var flag bool
|
|
if c.isGetCurTime() {
|
|
// if date time format is not support, use time.RFC3339
|
|
if c.value, flag = wb.Format(c.Header.IndexXf, 0); !flag {
|
|
c.value = parseTime(0, time.RFC3339)
|
|
}
|
|
} else if c.isRef() {
|
|
if ref {
|
|
var ws = -1
|
|
var find bool
|
|
var rIdx uint16
|
|
var cIdx uint16
|
|
|
|
if 0x07 == c.Bts[0] {
|
|
var exi = ByteToUint16(c.Bts[3:5])
|
|
rIdx = ByteToUint16(c.Bts[5:7])
|
|
cIdx = 0x00FF & ByteToUint16(c.Bts[7:9])
|
|
if exi <= wb.ref.Num {
|
|
ws = int(wb.ref.Info[int(exi)].FirstSheetIndex)
|
|
}
|
|
} else {
|
|
ws = c.ws
|
|
rIdx = ByteToUint16(c.Bts[3:5])
|
|
cIdx = 0x00FF & ByteToUint16(c.Bts[5:7])
|
|
}
|
|
|
|
if ws < len(wb.sheets) {
|
|
if row := wb.GetSheet(ws).Row(int(rIdx)); nil != row {
|
|
find = true
|
|
c.value = row.Col(int(cIdx))
|
|
}
|
|
}
|
|
if !find {
|
|
c.value = "#REF!"
|
|
}
|
|
} else {
|
|
c.parsed = false
|
|
}
|
|
} else {
|
|
c.value, flag = wb.Format(c.Header.IndexXf, c.Header.Value())
|
|
if !flag {
|
|
c.value = strconv.FormatFloat(c.Header.Value(), 'f', -1, 64)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// isRef return cell is reference to other cell
|
|
func (c *FormulaCol) isRef() bool {
|
|
if 0x05 == c.Bts[0] && (0x24 == c.Bts[2] || 0x44 == c.Bts[2] || 0x64 == c.Bts[2]) {
|
|
return true
|
|
} else if 0x07 == c.Bts[0] && (0x3A == c.Bts[2] || 0x5A == c.Bts[2] || 0x7A == c.Bts[2]) {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// isGetCurTime return cell value is get current date or datetime flag
|
|
func (c *FormulaCol) isGetCurTime() bool {
|
|
var ret bool
|
|
var next byte
|
|
|
|
if 0x19 == c.Bts[2] && (0x21 == c.Bts[6] || 0x41 == c.Bts[6] || 0x61 == c.Bts[6]) {
|
|
next = c.Bts[7]
|
|
} else if 0x21 == c.Bts[2] || 0x41 == c.Bts[2] || 0x61 == c.Bts[2] {
|
|
next = c.Bts[3]
|
|
}
|
|
|
|
if 0x4A == next || 0xDD == next {
|
|
ret = true
|
|
}
|
|
|
|
return ret
|
|
}
|
|
|
|
type RkCol struct {
|
|
Col
|
|
Xfrk XfRk
|
|
}
|
|
|
|
func (c *RkCol) Debug(wb *WorkBook) {
|
|
fmt.Printf("rk col dump:%#+v\n", c)
|
|
}
|
|
|
|
func (c *RkCol) String(wb *WorkBook) []string {
|
|
return []string{c.Xfrk.String(wb)}
|
|
}
|
|
|
|
type LabelsstCol struct {
|
|
Col
|
|
Xf uint16
|
|
Sst uint32
|
|
}
|
|
|
|
func (c *LabelsstCol) Debug(wb *WorkBook) {
|
|
fmt.Printf("label sst col dump:%#+v\n", c)
|
|
}
|
|
|
|
func (c *LabelsstCol) String(wb *WorkBook) []string {
|
|
if wb.Debug {
|
|
fmt.Println("metlabel sst col dump:", c.Sst, wb.sst[int(c.Sst)])
|
|
}
|
|
|
|
return []string{wb.sst[int(c.Sst)]}
|
|
}
|
|
|
|
type labelCol struct {
|
|
BlankCol
|
|
Str string
|
|
}
|
|
|
|
func (c *labelCol) Debug(wb *WorkBook) {
|
|
fmt.Printf("label col dump:%#+v\n", c)
|
|
}
|
|
|
|
func (c *labelCol) String(wb *WorkBook) []string {
|
|
return []string{c.Str}
|
|
}
|
|
|
|
type BlankCol struct {
|
|
Col
|
|
Xf uint16
|
|
}
|
|
|
|
func (c *BlankCol) Debug(wb *WorkBook) {
|
|
fmt.Printf("blank col dump:%#+v\n", c)
|
|
}
|
|
|
|
func (c *BlankCol) String(wb *WorkBook) []string {
|
|
return []string{""}
|
|
}
|