Add Formula & Format support
This commit is contained in:
329
col.go
329
col.go
@@ -1,17 +1,31 @@
|
||||
package xls
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
"time"
|
||||
|
||||
"github.com/extrame/goyymmdd"
|
||||
)
|
||||
|
||||
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
|
||||
@@ -26,6 +40,10 @@ 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
|
||||
}
|
||||
@@ -42,39 +60,12 @@ func (c *Col) String(wb *WorkBook) []string {
|
||||
return []string{"default"}
|
||||
}
|
||||
|
||||
type XfRk struct {
|
||||
Index uint16
|
||||
Rk RK
|
||||
}
|
||||
|
||||
func (xf *XfRk) String(wb *WorkBook) string {
|
||||
idx := int(xf.Index)
|
||||
if len(wb.Xfs) > idx {
|
||||
fNo := wb.Xfs[idx].formatNo()
|
||||
if fNo >= 164 { // user defined format
|
||||
if formatter := wb.Formats[fNo]; formatter != nil {
|
||||
i, f, isFloat := xf.Rk.number()
|
||||
if !isFloat {
|
||||
f = float64(i)
|
||||
}
|
||||
t := timeFromExcelTime(f, wb.dateMode == 1)
|
||||
return yymmdd.Format(t, formatter.str)
|
||||
}
|
||||
// see http://www.openoffice.org/sc/excelfileformat.pdf Page #174
|
||||
} else if 14 <= fNo && fNo <= 17 || fNo == 22 || 27 <= fNo && fNo <= 36 || 50 <= fNo && fNo <= 58 { // jp. date format
|
||||
i, f, isFloat := xf.Rk.number()
|
||||
if !isFloat {
|
||||
f = float64(i)
|
||||
}
|
||||
t := timeFromExcelTime(f, wb.dateMode == 1)
|
||||
return t.Format(time.RFC3339) //TODO it should be international
|
||||
}
|
||||
}
|
||||
return xf.Rk.String()
|
||||
}
|
||||
|
||||
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
|
||||
@@ -97,22 +88,40 @@ func (rk RK) number() (intNum int64, floatNum float64, isFloat bool) {
|
||||
return int64(val), 0, false
|
||||
}
|
||||
|
||||
func (rk RK) String() string {
|
||||
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)
|
||||
}
|
||||
|
||||
var ErrIsInt = fmt.Errorf("is int")
|
||||
type XfRk struct {
|
||||
Index uint16
|
||||
Rk RK
|
||||
}
|
||||
|
||||
func (rk RK) Float() (float64, error) {
|
||||
_, f, isFloat := rk.number()
|
||||
if !isFloat {
|
||||
return 0, ErrIsInt
|
||||
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 f, nil
|
||||
|
||||
return xf.Rk.String(wb)
|
||||
}
|
||||
|
||||
type MulrkCol struct {
|
||||
@@ -121,16 +130,24 @@ type MulrkCol struct {
|
||||
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 := 0; i < len(c.Xfrks); i++ {
|
||||
xfrk := c.Xfrks[i]
|
||||
res[i] = xfrk.String(wb)
|
||||
for i, v := range c.Xfrks {
|
||||
res[i] = v.String(wb)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
@@ -140,6 +157,10 @@ type MulBlankCol struct {
|
||||
LastColB uint16
|
||||
}
|
||||
|
||||
func (c *MulBlankCol) Debug(wb *WorkBook) {
|
||||
fmt.Printf("mul blank dump:%#+v\n", c)
|
||||
}
|
||||
|
||||
func (c *MulBlankCol) LastCol() uint16 {
|
||||
return c.LastColB
|
||||
}
|
||||
@@ -154,23 +175,209 @@ type NumberCol struct {
|
||||
Float float64
|
||||
}
|
||||
|
||||
func (c *NumberCol) Debug(wb *WorkBook) {
|
||||
fmt.Printf("number col dump:%#+v\n", c)
|
||||
}
|
||||
|
||||
func (c *NumberCol) String(wb *WorkBook) []string {
|
||||
if v, ok := wb.Format(c.Index, c.Float); ok {
|
||||
return []string{v}
|
||||
}
|
||||
|
||||
return []string{strconv.FormatFloat(c.Float, 'f', -1, 64)}
|
||||
}
|
||||
|
||||
type FormulaCol struct {
|
||||
Header struct {
|
||||
Col
|
||||
IndexXf uint16
|
||||
Result [8]byte
|
||||
Flags uint16
|
||||
_ uint32
|
||||
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 = ((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, float64(20-exp))
|
||||
|
||||
if mantissalow1 != 0 {
|
||||
value += 1 / math.Pow(2, float64(21-exp))
|
||||
}
|
||||
Bts []byte
|
||||
|
||||
value += float64(mantissalow2) / math.Pow(2, float64(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 {
|
||||
return []string{"FormulaCol"}
|
||||
if !c.parsed {
|
||||
c.parse(wb, true)
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -178,6 +385,10 @@ type RkCol struct {
|
||||
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)}
|
||||
}
|
||||
@@ -188,6 +399,10 @@ type LabelsstCol struct {
|
||||
Sst uint32
|
||||
}
|
||||
|
||||
func (c *LabelsstCol) Debug(wb *WorkBook) {
|
||||
fmt.Printf("label sst col dump:%#+v\n", c)
|
||||
}
|
||||
|
||||
func (c *LabelsstCol) String(wb *WorkBook) []string {
|
||||
return []string{wb.sst[int(c.Sst)]}
|
||||
}
|
||||
@@ -197,6 +412,10 @@ type labelCol struct {
|
||||
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}
|
||||
}
|
||||
@@ -206,6 +425,10 @@ type BlankCol struct {
|
||||
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{""}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user