20
0

Add Formula & Format support

This commit is contained in:
chen.s.g 2018-04-05 21:28:01 +08:00
parent d1d6f84447
commit 574bf55ec4
11 changed files with 786 additions and 222 deletions

View File

@ -1,8 +1,9 @@
# xls # xls
[![GoDoc](https://godoc.org/github.com/extrame/xls?status.svg)](https://godoc.org/github.com/extrame/xls) [![GoDoc](https://godoc.org/github.com/csg800/xls?status.svg)](https://godoc.org/github.com/csg800/xls)
Pure Golang xls library writen by [Rongshu Tech(chinese)](http://www.rongshu.tech). Pure Golang xls library writen by [Rongshu Tech(chinese)](http://www.rongshu.tech).
Add Formula & Format support by [chen.s.g] (http://www.imohe.com)
Thanks for contributions from Tamás Gulácsi, sergeilem. Thanks for contributions from Tamás Gulácsi, sergeilem.

View File

@ -46,6 +46,10 @@ type HyperLink struct {
IsUrl bool IsUrl bool
} }
func (h *HyperLink) Debug(wb *WorkBook) {
fmt.Printf("hyper link col dump:%#+v\n", h)
}
//get the hyperlink string, use the public variable Url to get the original Url //get the hyperlink string, use the public variable Url to get the original Url
func (h *HyperLink) String(wb *WorkBook) []string { func (h *HyperLink) String(wb *WorkBook) []string {
res := make([]string, h.LastColB-h.FristColB+1) res := make([]string, h.LastColB-h.FristColB+1)

329
col.go
View File

@ -1,17 +1,31 @@
package xls package xls
import ( import (
"errors"
"fmt" "fmt"
"math" "math"
"strconv" "strconv"
"time" "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 //content type
type contentHandler interface { type contentHandler interface {
Debug(wb *WorkBook)
String(*WorkBook) []string String(*WorkBook) []string
FirstCol() uint16 FirstCol() uint16
LastCol() uint16 LastCol() uint16
@ -26,6 +40,10 @@ type Coler interface {
Row() uint16 Row() uint16
} }
func (c *Col) Debug(wb *WorkBook) {
fmt.Printf("col dump:%#+v\n", c)
}
func (c *Col) Row() uint16 { func (c *Col) Row() uint16 {
return c.RowB return c.RowB
} }
@ -42,39 +60,12 @@ func (c *Col) String(wb *WorkBook) []string {
return []string{"default"} 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 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) { func (rk RK) number() (intNum int64, floatNum float64, isFloat bool) {
multiplied := rk & 1 multiplied := rk & 1
isInt := rk & 2 isInt := rk & 2
@ -97,22 +88,40 @@ func (rk RK) number() (intNum int64, floatNum float64, isFloat bool) {
return int64(val), 0, false 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() i, f, isFloat := rk.number()
if isFloat { if isFloat {
return strconv.FormatFloat(f, 'f', -1, 64) return strconv.FormatFloat(f, 'f', -1, 64)
} }
return strconv.FormatInt(i, 10) return strconv.FormatInt(i, 10)
} }
var ErrIsInt = fmt.Errorf("is int") type XfRk struct {
Index uint16
Rk RK
}
func (rk RK) Float() (float64, error) { func (xf *XfRk) Debug(wb *WorkBook) {
_, f, isFloat := rk.number() fmt.Printf("xfrk dump:%#+v\n", wb.Xfs[xf.Index])
if !isFloat { xf.Rk.Debug(wb)
return 0, ErrIsInt }
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 { type MulrkCol struct {
@ -121,16 +130,24 @@ type MulrkCol struct {
LastColB uint16 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 { func (c *MulrkCol) LastCol() uint16 {
return c.LastColB return c.LastColB
} }
func (c *MulrkCol) String(wb *WorkBook) []string { func (c *MulrkCol) String(wb *WorkBook) []string {
var res = make([]string, len(c.Xfrks)) var res = make([]string, len(c.Xfrks))
for i := 0; i < len(c.Xfrks); i++ { for i, v := range c.Xfrks {
xfrk := c.Xfrks[i] res[i] = v.String(wb)
res[i] = xfrk.String(wb)
} }
return res return res
} }
@ -140,6 +157,10 @@ type MulBlankCol struct {
LastColB uint16 LastColB uint16
} }
func (c *MulBlankCol) Debug(wb *WorkBook) {
fmt.Printf("mul blank dump:%#+v\n", c)
}
func (c *MulBlankCol) LastCol() uint16 { func (c *MulBlankCol) LastCol() uint16 {
return c.LastColB return c.LastColB
} }
@ -154,23 +175,209 @@ type NumberCol struct {
Float float64 Float float64
} }
func (c *NumberCol) Debug(wb *WorkBook) {
fmt.Printf("number col dump:%#+v\n", c)
}
func (c *NumberCol) String(wb *WorkBook) []string { 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)} return []string{strconv.FormatFloat(c.Float, 'f', -1, 64)}
} }
type FormulaCol struct { type FormulaColHeader struct {
Header struct { Col
Col IndexXf uint16
IndexXf uint16 Result [8]byte
Result [8]byte Flags uint16
Flags uint16 _ uint32
_ 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 { 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 { type RkCol struct {
@ -178,6 +385,10 @@ type RkCol struct {
Xfrk XfRk Xfrk XfRk
} }
func (c *RkCol) Debug(wb *WorkBook) {
fmt.Printf("rk col dump:%#+v\n", c)
}
func (c *RkCol) String(wb *WorkBook) []string { func (c *RkCol) String(wb *WorkBook) []string {
return []string{c.Xfrk.String(wb)} return []string{c.Xfrk.String(wb)}
} }
@ -188,6 +399,10 @@ type LabelsstCol struct {
Sst uint32 Sst uint32
} }
func (c *LabelsstCol) Debug(wb *WorkBook) {
fmt.Printf("label sst col dump:%#+v\n", c)
}
func (c *LabelsstCol) String(wb *WorkBook) []string { func (c *LabelsstCol) String(wb *WorkBook) []string {
return []string{wb.sst[int(c.Sst)]} return []string{wb.sst[int(c.Sst)]}
} }
@ -197,6 +412,10 @@ type labelCol struct {
Str string Str string
} }
func (c *labelCol) Debug(wb *WorkBook) {
fmt.Printf("label col dump:%#+v\n", c)
}
func (c *labelCol) String(wb *WorkBook) []string { func (c *labelCol) String(wb *WorkBook) []string {
return []string{c.Str} return []string{c.Str}
} }
@ -206,6 +425,10 @@ type BlankCol struct {
Xf uint16 Xf uint16
} }
func (c *BlankCol) Debug(wb *WorkBook) {
fmt.Printf("blank col dump:%#+v\n", c)
}
func (c *BlankCol) String(wb *WorkBook) []string { func (c *BlankCol) String(wb *WorkBook) []string {
return []string{""} return []string{""}
} }

79
date.go
View File

@ -5,37 +5,57 @@ import (
"time" "time"
) )
const MJD_0 float64 = 2400000.5 // timeLocationUTC defined the UTC time location.
const MJD_JD2000 float64 = 51544.5 var timeLocationUTC, _ = time.LoadLocation("UTC")
// timeToUTCTime provides function to convert time to UTC time.
func timeToUTCTime(t time.Time) time.Time {
return time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), timeLocationUTC)
}
// timeToExcelTime provides function to convert time to Excel time.
func timeToExcelTime(t time.Time) float64 {
return float64(t.UnixNano())/8.64e13 + 25569.0
}
// shiftJulianToNoon provides function to process julian date to noon.
func shiftJulianToNoon(julianDays, julianFraction float64) (float64, float64) { func shiftJulianToNoon(julianDays, julianFraction float64) (float64, float64) {
switch { switch {
case -0.5 < julianFraction && julianFraction < 0.5: case -0.5 < julianFraction && julianFraction < 0.5:
julianFraction += 0.5 julianFraction += 0.5
case julianFraction >= 0.5: case julianFraction >= 0.5:
julianDays += 1 julianDays++
julianFraction -= 0.5 julianFraction -= 0.5
case julianFraction <= -0.5: case julianFraction <= -0.5:
julianDays -= 1 julianDays--
julianFraction += 1.5 julianFraction += 1.5
} }
return julianDays, julianFraction return julianDays, julianFraction
} }
// Return the integer values for hour, minutes, seconds and // fractionOfADay provides function to return the integer values for hour,
// nanoseconds that comprised a given fraction of a day. // minutes, seconds and nanoseconds that comprised a given fraction of a day.
// values would round to 1 us.
func fractionOfADay(fraction float64) (hours, minutes, seconds, nanoseconds int) { func fractionOfADay(fraction float64) (hours, minutes, seconds, nanoseconds int) {
f := 5184000000000000 * fraction
nanoseconds = int(math.Mod(f, 1000000000)) const (
f = f / 1000000000 c1us = 1e3
seconds = int(math.Mod(f, 60)) c1s = 1e9
f = f / 3600 c1day = 24 * 60 * 60 * c1s
minutes = int(math.Mod(f, 60)) )
f = f / 60
hours = int(f) frac := int64(c1day*fraction + c1us/2)
return hours, minutes, seconds, nanoseconds nanoseconds = int((frac%c1s)/c1us) * c1us
frac /= c1s
seconds = int(frac % 60)
frac /= 60
minutes = int(frac % 60)
hours = int(frac / 60)
return
} }
// julianDateToGregorianTime provides function to convert julian date to
// gregorian time.
func julianDateToGregorianTime(part1, part2 float64) time.Time { func julianDateToGregorianTime(part1, part2 float64) time.Time {
part1I, part1F := math.Modf(part1) part1I, part1F := math.Modf(part1)
part2I, part2F := math.Modf(part2) part2I, part2F := math.Modf(part2)
@ -47,13 +67,12 @@ func julianDateToGregorianTime(part1, part2 float64) time.Time {
return time.Date(year, time.Month(month), day, hours, minutes, seconds, nanoseconds, time.UTC) return time.Date(year, time.Month(month), day, hours, minutes, seconds, nanoseconds, time.UTC)
} }
// By this point generations of programmers have repeated the // By this point generations of programmers have repeated the algorithm sent to
// algorithm sent to the editor of "Communications of the ACM" in 1968 // the editor of "Communications of the ACM" in 1968 (published in CACM, volume
// (published in CACM, volume 11, number 10, October 1968, p.657). // 11, number 10, October 1968, p.657). None of those programmers seems to have
// None of those programmers seems to have found it necessary to // found it necessary to explain the constants or variable names set out by
// explain the constants or variable names set out by Henry F. Fliegel // Henry F. Fliegel and Thomas C. Van Flandern. Maybe one day I'll buy that
// and Thomas C. Van Flandern. Maybe one day I'll buy that jounal and // jounal and expand an explanation here - that day is not today.
// expand an explanation here - that day is not today.
func doTheFliegelAndVanFlandernAlgorithm(jd int) (day, month, year int) { func doTheFliegelAndVanFlandernAlgorithm(jd int) (day, month, year int) {
l := jd + 68569 l := jd + 68569
n := (4 * l) / 146097 n := (4 * l) / 146097
@ -68,24 +87,26 @@ func doTheFliegelAndVanFlandernAlgorithm(jd int) (day, month, year int) {
return d, m, y return d, m, y
} }
// Convert an excelTime representation (stored as a floating point number) to a time.Time. // timeFromExcelTime provides function to convert an excelTime representation
// (stored as a floating point number) to a time.Time.
func timeFromExcelTime(excelTime float64, date1904 bool) time.Time { func timeFromExcelTime(excelTime float64, date1904 bool) time.Time {
var date time.Time var date time.Time
var intPart int64 = int64(excelTime) var intPart = int64(excelTime)
// Excel uses Julian dates prior to March 1st 1900, and // Excel uses Julian dates prior to March 1st 1900, and Gregorian
// Gregorian thereafter. // thereafter.
if intPart <= 61 { if intPart <= 61 {
const OFFSET1900 = 15018.0 const OFFSET1900 = 15018.0
const OFFSET1904 = 16480.0 const OFFSET1904 = 16480.0
const MJD0 float64 = 2400000.5
var date time.Time var date time.Time
if date1904 { if date1904 {
date = julianDateToGregorianTime(MJD_0+OFFSET1904, excelTime) date = julianDateToGregorianTime(MJD0, excelTime+OFFSET1904)
} else { } else {
date = julianDateToGregorianTime(MJD_0+OFFSET1900, excelTime) date = julianDateToGregorianTime(MJD0, excelTime+OFFSET1900)
} }
return date return date
} }
var floatPart float64 = excelTime - float64(intPart) var floatPart = excelTime - float64(intPart)
var dayNanoSeconds float64 = 24 * 60 * 60 * 1000 * 1000 * 1000 var dayNanoSeconds float64 = 24 * 60 * 60 * 1000 * 1000 * 1000
if date1904 { if date1904 {
date = time.Date(1904, 1, 1, 0, 0, 0, 0, time.UTC) date = time.Date(1904, 1, 1, 0, 0, 0, 0, time.UTC)

View File

@ -2,6 +2,7 @@ package xls
import ( import (
"fmt" "fmt"
"testing"
) )
func ExampleOpen() { func ExampleOpen() {
@ -35,3 +36,23 @@ func ExampleWorkBook_GetSheet() {
} }
} }
} }
func BenchmarkGetSheet(b *testing.B) {
for i := 0; i < b.N; i++ {
if xlFile, err := Open("Table.xls", "utf-8"); err == nil {
for i := 0; i < xlFile.NumSheets(); i++ {
xlFile.GetSheet(i)
}
}
}
}
func BenchmarkGetSheetWithBuffer(b *testing.B) {
for i := 0; i < b.N; i++ {
if xlFile, err := OpenWithBuffer("Table.xls", "utf-8"); err == nil {
for i := 0; i < xlFile.NumSheets(); i++ {
xlFile.GetSheet(i)
}
}
}
}

221
format.go
View File

@ -1,9 +1,228 @@
package xls package xls
import (
"regexp"
"strconv"
"strings"
"time"
)
// Excel styles can reference number formats that are built-in, all of which
// have an id less than 164. This is a possibly incomplete list comprised of as
// many of them as I could find.
var builtInNumFmt = map[uint16]string{
0: "general",
1: "0",
2: "0.00",
3: "#,##0",
4: "#,##0.00",
9: "0%",
10: "0.00%",
11: "0.00e+00",
12: "# ?/?",
13: "# ??/??",
14: "mm-dd-yy",
15: "d-mmm-yy",
16: "d-mmm",
17: "mmm-yy",
18: "h:mm am/pm",
19: "h:mm:ss am/pm",
20: "h:mm",
21: "h:mm:ss",
22: "m/d/yy h:mm",
37: "#,##0 ;(#,##0)",
38: "#,##0 ;[red](#,##0)",
39: "#,##0.00;(#,##0.00)",
40: "#,##0.00;[red](#,##0.00)",
41: `_(* #,##0_);_(* \(#,##0\);_(* "-"_);_(@_)`,
42: `_("$"* #,##0_);_("$* \(#,##0\);_("$"* "-"_);_(@_)`,
43: `_(* #,##0.00_);_(* \(#,##0.00\);_(* "-"??_);_(@_)`,
44: `_("$"* #,##0.00_);_("$"* \(#,##0.00\);_("$"* "-"??_);_(@_)`,
45: "mm:ss",
46: "[h]:mm:ss",
47: "mmss.0",
48: "##0.0e+0",
49: "@",
58: time.RFC3339,
}
// Excel date time mapper to go system
var dateTimeMapper = []struct{ xls, golang string }{
{"yyyy", "2006"},
{"yy", "06"},
{"mmmm", "%%%%"},
{"dddd", "&&&&"},
{"dd", "02"},
{"d", "2"},
{"mmm", "Jan"},
{"mmss", "0405"},
{"ss", "05"},
{"mm:", "04:"},
{":mm", ":04"},
{"mm", "01"},
{"am/pm", "pm"},
{"m/", "1/"},
{"%%%%", "January"},
{"&&&&", "Monday"},
}
// Format value interface
type Format struct { type Format struct {
Head struct { Head struct {
Index uint16 Index uint16
Size uint16 Size uint16
} }
str string Raw []string
bts int
vType int
}
// Prepare format meta data
func (f *Format) Prepare() {
var regexColor = regexp.MustCompile("^\\[[a-zA-Z]+\\]")
var regexFraction = regexp.MustCompile("#\\,?#*")
for k, v := range f.Raw {
// In Excel formats, "_" is used to add spacing, which we can't do in HTML
v = strings.Replace(v, "_", "", -1)
// Some non-number characters are escaped with \, which we don't need
v = strings.Replace(v, "\\", "", -1)
// Some non-number strings are quoted, so we'll get rid of the quotes, likewise any positional * symbols
v = strings.Replace(v, "*", "", -1)
v = strings.Replace(v, "\"", "", -1)
// strip ()
v = strings.Replace(v, "(", "", -1)
v = strings.Replace(v, ")", "", -1)
// strip color information
v = regexColor.ReplaceAllString(v, "")
// Strip #
v = regexFraction.ReplaceAllString(v, "")
if 0 == f.vType {
if regexp.MustCompile("^(\\[\\$[A-Z]*-[0-9A-F]*\\])*[hmsdy]").MatchString(v) {
f.vType = TYPE_DATETIME
} else if strings.HasSuffix(v, "%") {
f.vType = TYPE_PERCENTAGE
} else if strings.HasPrefix(v, "$") || strings.HasPrefix(v, "¥") {
f.vType = TYPE_CURRENCY
}
}
f.Raw[k] = strings.Trim(v, "\r\n\t ")
}
if 0 == f.vType {
f.vType = TYPE_NUMERIC
}
if TYPE_NUMERIC == f.vType || TYPE_CURRENCY == f.vType || TYPE_PERCENTAGE == f.vType {
var t []string
if t = strings.SplitN(f.Raw[0], ".", 2); 2 == len(t) {
f.bts = strings.Count(t[1], "")
if f.bts > 0 {
f.bts = f.bts - 1
}
}
}
}
// String format content to spec string
// see http://www.openoffice.org/sc/excelfileformat.pdf Page #174
func (f *Format) String(v float64) string {
var ret string
switch f.vType {
case TYPE_NUMERIC:
if 0 == f.bts {
ret = strconv.FormatInt(int64(v), 10)
} else {
ret = strconv.FormatFloat(v, 'f', f.bts, 64)
}
case TYPE_CURRENCY:
if 0 == f.bts {
ret = strconv.FormatInt(int64(v), 10)
} else {
ret = strconv.FormatFloat(v, 'f', f.bts, 64)
}
case TYPE_PERCENTAGE:
if 0 == f.bts {
ret = strconv.FormatInt(int64(v)*100, 10) + "%"
} else {
ret = strconv.FormatFloat(v*100, 'f', f.bts, 64) + "%"
}
case TYPE_DATETIME:
ret = parseTime(v, f.Raw[0])
default:
ret = strconv.FormatFloat(v, 'f', -1, 64)
}
return ret
}
// ByteToUint32 Read 32-bit unsigned integer
func ByteToUint32(b []byte) uint32 {
return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
}
// ByteToUint16 Read 16-bit unsigned integer
func ByteToUint16(b []byte) uint16 {
return (uint16(b[0]) | (uint16(b[1]) << 8))
}
// parseTime provides function to returns a string parsed using time.Time.
// Replace Excel placeholders with Go time placeholders. For example, replace
// yyyy with 2006. These are in a specific order, due to the fact that m is used
// in month, minute, and am/pm. It would be easier to fix that with regular
// expressions, but if it's possible to keep this simple it would be easier to
// maintain. Full-length month and days (e.g. March, Tuesday) have letters in
// them that would be replaced by other characters below (such as the 'h' in
// March, or the 'd' in Tuesday) below. First we convert them to arbitrary
// characters unused in Excel Date formats, and then at the end, turn them to
// what they should actually be.
// Based off: http://www.ozgrid.com/Excel/CustomFormats.htm
func parseTime(v float64, f string) string {
var val time.Time
if 0 == v {
val = time.Now()
} else {
val = timeFromExcelTime(v, false)
}
// It is the presence of the "am/pm" indicator that determines if this is
// a 12 hour or 24 hours time format, not the number of 'h' characters.
if is12HourTime(f) {
f = strings.Replace(f, "hh", "03", 1)
f = strings.Replace(f, "h", "3", 1)
} else {
f = strings.Replace(f, "hh", "15", 1)
f = strings.Replace(f, "h", "15", 1)
}
for _, repl := range dateTimeMapper {
f = strings.Replace(f, repl.xls, repl.golang, 1)
}
// If the hour is optional, strip it out, along with the possible dangling
// colon that would remain.
if val.Hour() < 1 {
f = strings.Replace(f, "]:", "]", 1)
f = strings.Replace(f, "[03]", "", 1)
f = strings.Replace(f, "[3]", "", 1)
f = strings.Replace(f, "[15]", "", 1)
} else {
f = strings.Replace(f, "[3]", "3", 1)
f = strings.Replace(f, "[15]", "15", 1)
}
return val.Format(f)
}
// is12HourTime checks whether an Excel time format string is a 12 hours form.
func is12HourTime(format string) bool {
return strings.Contains(format, "am/pm") || strings.Contains(format, "AM/PM") || strings.Contains(format, "a/p") || strings.Contains(format, "A/P")
} }

29
row.go
View File

@ -2,8 +2,8 @@ package xls
type rowInfo struct { type rowInfo struct {
Index uint16 Index uint16
Fcell uint16 First uint16
Lcell uint16 Last uint16
Height uint16 Height uint16
Notused uint16 Notused uint16
Notused2 uint16 Notused2 uint16
@ -20,27 +20,30 @@ type Row struct {
//Col Get the Nth Col from the Row, if has not, return nil. //Col Get the Nth Col from the Row, if has not, return nil.
//Suggest use Has function to test it. //Suggest use Has function to test it.
func (r *Row) Col(i int) string { func (r *Row) Col(i int) string {
serial := uint16(i) var val string
var serial = uint16(i)
if ch, ok := r.cols[serial]; ok { if ch, ok := r.cols[serial]; ok {
strs := ch.String(r.wb) val = ch.String(r.wb)[0]
return strs[0]
} else { } else {
for _, v := range r.cols { for _, v := range r.cols {
if v.FirstCol() <= serial && v.LastCol() >= serial { if v.FirstCol() <= serial && v.LastCol() >= serial {
strs := v.String(r.wb) val = v.String(r.wb)[serial-v.FirstCol()]
return strs[serial-v.FirstCol()]
break
} }
} }
} }
return ""
}
//LastCol Get the number of Last Col of the Row. return val
func (r *Row) LastCol() int {
return int(r.info.Lcell)
} }
//FirstCol Get the number of First Col of the Row. //FirstCol Get the number of First Col of the Row.
func (r *Row) FirstCol() int { func (r *Row) FirstCol() int {
return int(r.info.Fcell) return int(r.info.First)
}
//LastCol Get the number of Last Col of the Row.
func (r *Row) LastCol() int {
return int(r.info.Last)
} }

View File

@ -4,23 +4,23 @@ import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"io" "io"
"os" "strings"
"unicode/utf16" "unicode/utf16"
) )
//xls workbook type //xls workbook type
type WorkBook struct { type WorkBook struct {
Is5ver bool Is5ver bool
Type uint16 Type uint16
Codepage uint16 Codepage uint16
Xfs []st_xf_data Xfs []XF
Fonts []Font Fonts []Font
Formats map[uint16]*Format Formats map[uint16]*Format
//All the sheets from the workbook
sheets []*WorkSheet sheets []*WorkSheet
Author string Author string
rs io.ReadSeeker rs io.ReadSeeker
sst []string sst []string
ref *extSheetRef
continue_utf16 uint16 continue_utf16 uint16
continue_rich uint16 continue_rich uint16
continue_apsb uint32 continue_apsb uint32
@ -29,62 +29,50 @@ type WorkBook struct {
//read workbook from ole2 file //read workbook from ole2 file
func newWorkBookFromOle2(rs io.ReadSeeker) *WorkBook { func newWorkBookFromOle2(rs io.ReadSeeker) *WorkBook {
wb := new(WorkBook) var wb = &WorkBook{
wb.Formats = make(map[uint16]*Format) rs: rs,
// wb.bts = bts ref: new(extSheetRef),
wb.rs = rs sheets: make([]*WorkSheet, 0),
wb.sheets = make([]*WorkSheet, 0) Formats: make(map[uint16]*Format),
wb.Parse(rs) }
wb.parse(rs)
wb.prepare()
return wb return wb
} }
func (w *WorkBook) Parse(buf io.ReadSeeker) { func (w *WorkBook) parse(buf io.ReadSeeker) {
b := new(bof) b := new(bof)
bof_pre := new(bof) bp := new(bof)
// buf := bytes.NewReader(bts)
offset := 0 offset := 0
for { for {
if err := binary.Read(buf, binary.LittleEndian, b); err == nil { if err := binary.Read(buf, binary.LittleEndian, b); err == nil {
bof_pre, b, offset = w.parseBof(buf, b, bof_pre, offset) bp, b, offset = w.parseBof(buf, b, bp, offset)
} else { } else {
break break
} }
} }
} }
func (w *WorkBook) addXf(xf st_xf_data) {
w.Xfs = append(w.Xfs, xf)
}
func (w *WorkBook) addFont(font *FontInfo, buf io.ReadSeeker) {
name, _ := w.get_string(buf, uint16(font.NameB))
w.Fonts = append(w.Fonts, Font{Info: font, Name: name})
}
func (w *WorkBook) addFormat(format *Format) {
if w.Formats == nil {
os.Exit(1)
}
w.Formats[format.Head.Index] = format
}
func (wb *WorkBook) parseBof(buf io.ReadSeeker, b *bof, pre *bof, offset_pre int) (after *bof, after_using *bof, offset int) { func (wb *WorkBook) parseBof(buf io.ReadSeeker, b *bof, pre *bof, offset_pre int) (after *bof, after_using *bof, offset int) {
after = b after = b
after_using = pre after_using = pre
var bts = make([]byte, b.Size) var bts = make([]byte, b.Size)
binary.Read(buf, binary.LittleEndian, bts) binary.Read(buf, binary.LittleEndian, bts)
buf_item := bytes.NewReader(bts) item := bytes.NewReader(bts)
switch b.Id { switch b.Id {
case 0x809: case 0x0809: // BOF
bif := new(biffHeader) bif := new(biffHeader)
binary.Read(buf_item, binary.LittleEndian, bif) binary.Read(item, binary.LittleEndian, bif)
if bif.Ver != 0x600 { if bif.Ver != 0x600 {
wb.Is5ver = true wb.Is5ver = true
} }
wb.Type = bif.Type wb.Type = bif.Type
case 0x042: // CODEPAGE case 0x0042: // CODEPAGE
binary.Read(buf_item, binary.LittleEndian, &wb.Codepage) binary.Read(item, binary.LittleEndian, &wb.Codepage)
case 0x3c: // CONTINUE case 0x3C: // CONTINUE
if pre.Id == 0xfc { if pre.Id == 0xfc {
var size uint16 var size uint16
var err error var err error
@ -92,12 +80,12 @@ func (wb *WorkBook) parseBof(buf io.ReadSeeker, b *bof, pre *bof, offset_pre int
size = wb.continue_utf16 size = wb.continue_utf16
wb.continue_utf16 = 0 wb.continue_utf16 = 0
} else { } else {
err = binary.Read(buf_item, binary.LittleEndian, &size) err = binary.Read(item, binary.LittleEndian, &size)
} }
for err == nil && offset_pre < len(wb.sst) { for err == nil && offset_pre < len(wb.sst) {
var str string var str string
if size > 0 { if size > 0 {
str, err = wb.get_string(buf_item, size) str, err = wb.parseString(item, size)
wb.sst[offset_pre] = wb.sst[offset_pre] + str wb.sst[offset_pre] = wb.sst[offset_pre] + str
} }
@ -106,23 +94,23 @@ func (wb *WorkBook) parseBof(buf io.ReadSeeker, b *bof, pre *bof, offset_pre int
} }
offset_pre++ offset_pre++
err = binary.Read(buf_item, binary.LittleEndian, &size) err = binary.Read(item, binary.LittleEndian, &size)
} }
} }
offset = offset_pre offset = offset_pre
after = pre after = pre
after_using = b after_using = b
case 0xfc: // SST case 0x00FC: // SST
info := new(SstInfo) info := new(SstInfo)
binary.Read(buf_item, binary.LittleEndian, info) binary.Read(item, binary.LittleEndian, info)
wb.sst = make([]string, info.Count) wb.sst = make([]string, info.Count)
var size uint16 var size uint16
var i = 0 var i = 0
for ; i < int(info.Count); i++ { for ; i < int(info.Count); i++ {
var err error var err error
if err = binary.Read(buf_item, binary.LittleEndian, &size); err == nil { if err = binary.Read(item, binary.LittleEndian, &size); err == nil {
var str string var str string
str, err = wb.get_string(buf_item, size) str, err = wb.parseString(item, size)
wb.sst[i] = wb.sst[i] + str wb.sst[i] = wb.sst[i] + str
} }
@ -131,41 +119,90 @@ func (wb *WorkBook) parseBof(buf io.ReadSeeker, b *bof, pre *bof, offset_pre int
} }
} }
offset = i offset = i
case 0x85: // bOUNDSHEET case 0x0085: // SHEET
var bs = new(boundsheet) var bs = new(boundsheet)
binary.Read(buf_item, binary.LittleEndian, bs) binary.Read(item, binary.LittleEndian, bs)
// different for BIFF5 and BIFF8 // different for BIFF5 and BIFF8
wb.addSheet(bs, buf_item) wb.addSheet(bs, item)
case 0x0e0: // XF case 0x0017: // EXTERNSHEET
if !wb.Is5ver {
binary.Read(item, binary.LittleEndian, &wb.ref.Num)
wb.ref.Info = make([]ExtSheetInfo, wb.ref.Num)
binary.Read(item, binary.LittleEndian, &wb.ref.Info)
}
case 0x00e0: // XF
if wb.Is5ver { if wb.Is5ver {
xf := new(Xf5) xf := new(Xf5)
binary.Read(buf_item, binary.LittleEndian, xf) binary.Read(item, binary.LittleEndian, xf)
wb.addXf(xf) wb.addXf(xf)
} else { } else {
xf := new(Xf8) xf := new(Xf8)
binary.Read(buf_item, binary.LittleEndian, xf) binary.Read(item, binary.LittleEndian, xf)
wb.addXf(xf) wb.addXf(xf)
} }
case 0x031: // FONT case 0x0031: // FONT
f := new(FontInfo) f := new(FontInfo)
binary.Read(buf_item, binary.LittleEndian, f) binary.Read(item, binary.LittleEndian, f)
wb.addFont(f, buf_item) wb.addFont(f, item)
case 0x41E: //FORMAT case 0x041E: //FORMAT
font := new(Format) format := new(Format)
binary.Read(buf_item, binary.LittleEndian, &font.Head) binary.Read(item, binary.LittleEndian, &format.Head)
font.str, _ = wb.get_string(buf_item, font.Head.Size) if raw, err := wb.parseString(item, format.Head.Size); nil == err && "" != raw {
wb.addFormat(font) format.Raw = strings.Split(raw, ";")
case 0x22: //DATEMODE } else {
binary.Read(buf_item, binary.LittleEndian, &wb.dateMode) format.Raw = []string{}
}
wb.addFormat(format)
case 0x0022: //DATEMODE
binary.Read(item, binary.LittleEndian, &wb.dateMode)
} }
return return
} }
func (w *WorkBook) get_string(buf io.ReadSeeker, size uint16) (res string, err error) { func (w *WorkBook) addXf(xf XF) {
w.Xfs = append(w.Xfs, xf)
}
func (w *WorkBook) addFont(font *FontInfo, buf io.ReadSeeker) {
name, _ := w.parseString(buf, uint16(font.NameB))
w.Fonts = append(w.Fonts, Font{Info: font, Name: name})
}
func (w *WorkBook) addFormat(format *Format) {
w.Formats[format.Head.Index] = format
}
func (w *WorkBook) addSheet(sheet *boundsheet, buf io.ReadSeeker) {
name, _ := w.parseString(buf, uint16(sheet.Name))
w.sheets = append(w.sheets, &WorkSheet{id: len(w.sheets), bs: sheet, Name: name, wb: w})
}
// prepare process workbook struct
func (w *WorkBook) prepare() {
for k, v := range builtInNumFmt {
if _, ok := w.Formats[k]; !ok {
w.Formats[k] = &Format{
Raw: strings.Split(v, ";"),
}
}
}
for _, v := range w.Formats {
v.Prepare()
}
}
//reading a sheet from the compress file to memory, you should call this before you try to get anything from sheet
func (w *WorkBook) prepareSheet(sheet *WorkSheet) {
w.rs.Seek(int64(sheet.bs.Filepos), 0)
sheet.parse(w.rs)
}
func (w *WorkBook) parseString(buf io.ReadSeeker, size uint16) (res string, err error) {
if w.Is5ver { if w.Is5ver {
var bts = make([]byte, size) var bts = make([]byte, size)
_, err = buf.Read(bts) _, err = buf.Read(bts)
res = string(bts) res = string(bytes.Trim(bts, "\r\n\t "))
} else { } else {
var richtext_num = uint16(0) var richtext_num = uint16(0)
var phonetic_size = uint32(0) var phonetic_size = uint32(0)
@ -190,7 +227,7 @@ func (w *WorkBook) get_string(buf io.ReadSeeker, size uint16) (res string, err e
err = binary.Read(buf, binary.LittleEndian, &bts[i]) err = binary.Read(buf, binary.LittleEndian, &bts[i])
} }
runes := utf16.Decode(bts[:i]) runes := utf16.Decode(bts[:i])
res = string(runes) res = strings.Trim(string(runes), "\r\n\t ")
if i < size { if i < size {
w.continue_utf16 = size - i + 1 w.continue_utf16 = size - i + 1
} }
@ -208,23 +245,21 @@ func (w *WorkBook) get_string(buf io.ReadSeeker, size uint16) (res string, err e
bts1[k] = uint16(v) bts1[k] = uint16(v)
} }
runes := utf16.Decode(bts1) runes := utf16.Decode(bts1)
res = string(runes) res = strings.Trim(string(runes), "\r\n\t ")
} }
if richtext_num > 0 { if richtext_num > 0 {
var bts []byte var bts []byte
var seek_size int64 var ss int64
if w.Is5ver { if w.Is5ver {
seek_size = int64(2 * richtext_num) ss = int64(2 * richtext_num)
} else { } else {
seek_size = int64(4 * richtext_num) ss = int64(4 * richtext_num)
} }
bts = make([]byte, seek_size) bts = make([]byte, ss)
err = binary.Read(buf, binary.LittleEndian, bts) err = binary.Read(buf, binary.LittleEndian, bts)
if err == io.EOF { if err == io.EOF {
w.continue_rich = richtext_num w.continue_rich = richtext_num
} }
// err = binary.Read(buf, binary.LittleEndian, bts)
} }
if phonetic_size > 0 { if phonetic_size > 0 {
var bts []byte var bts []byte
@ -238,18 +273,20 @@ func (w *WorkBook) get_string(buf io.ReadSeeker, size uint16) (res string, err e
return return
} }
func (w *WorkBook) addSheet(sheet *boundsheet, buf io.ReadSeeker) { // Format format value to string
name, _ := w.get_string(buf, uint16(sheet.Name)) func (w *WorkBook) Format(xf uint16, v float64) (string, bool) {
w.sheets = append(w.sheets, &WorkSheet{bs: sheet, Name: name, wb: w}) var val string
var idx = int(xf)
if len(w.Xfs) > idx {
if formatter := w.Formats[w.Xfs[idx].FormatNo()]; nil != formatter {
return formatter.String(v), true
}
}
return val, false
} }
//reading a sheet from the compress file to memory, you should call this before you try to get anything from sheet //GetSheet get one sheet by its number
func (w *WorkBook) prepareSheet(sheet *WorkSheet) {
w.rs.Seek(int64(sheet.bs.Filepos), 0)
sheet.parse(w.rs)
}
//Get one sheet by its number
func (w *WorkBook) GetSheet(num int) *WorkSheet { func (w *WorkBook) GetSheet(num int) *WorkSheet {
if num < len(w.sheets) { if num < len(w.sheets) {
s := w.sheets[num] s := w.sheets[num]
@ -257,9 +294,8 @@ func (w *WorkBook) GetSheet(num int) *WorkSheet {
w.prepareSheet(s) w.prepareSheet(s)
} }
return s return s
} else {
return nil
} }
return nil
} }
//Get the number of all sheets, look into example //Get the number of all sheets, look into example
@ -267,9 +303,9 @@ func (w *WorkBook) NumSheets() int {
return len(w.sheets) return len(w.sheets)
} }
//helper function to read all cells from file //ReadAllCells helper function to read all cells from file
//Notice: the max value is the limit of the max capacity of lines. //Notice: the max value is the limit of the max capacity of lines.
//Warning: the helper function will need big memeory if file is large. //Warning: the helper function will need big memory if file is large.
func (w *WorkBook) ReadAllCells(max int) (res [][]string) { func (w *WorkBook) ReadAllCells(max int) (res [][]string) {
res = make([][]string, 0) res = make([][]string, 0)
for _, sheet := range w.sheets { for _, sheet := range w.sheets {
@ -277,11 +313,11 @@ func (w *WorkBook) ReadAllCells(max int) (res [][]string) {
max = max - len(res) max = max - len(res)
w.prepareSheet(sheet) w.prepareSheet(sheet)
if sheet.MaxRow != 0 { if sheet.MaxRow != 0 {
leng := int(sheet.MaxRow) + 1 length := int(sheet.MaxRow) + 1
if max < leng { if max < length {
leng = max length = max
} }
temp := make([][]string, leng) temp := make([][]string, length)
for k, row := range sheet.rows { for k, row := range sheet.rows {
data := make([]string, 0) data := make([]string, 0)
if len(row.cols) > 0 { if len(row.cols) > 0 {
@ -295,7 +331,7 @@ func (w *WorkBook) ReadAllCells(max int) (res [][]string) {
data[col.FirstCol()+i] = str[i] data[col.FirstCol()+i] = str[i]
} }
} }
if leng > int(k) { if length > int(k) {
temp[k] = data temp[k] = data
} }
} }

View File

@ -2,7 +2,6 @@ package xls
import ( import (
"encoding/binary" "encoding/binary"
"fmt"
"io" "io"
"unicode/utf16" "unicode/utf16"
) )
@ -14,6 +13,18 @@ type boundsheet struct {
Name byte Name byte
} }
type extSheetRef struct {
Num uint16
Info []ExtSheetInfo
}
// ExtSheetInfo external sheet references provided for named cells
type ExtSheetInfo struct {
ExternalBookIndex uint16
FirstSheetIndex uint16
LastSheetIndex uint16
}
//WorkSheet in one WorkBook //WorkSheet in one WorkBook
type WorkSheet struct { type WorkSheet struct {
bs *boundsheet bs *boundsheet
@ -22,6 +33,7 @@ type WorkSheet struct {
rows map[uint16]*Row rows map[uint16]*Row
//NOTICE: this is the max row number of the sheet, so it should be count -1 //NOTICE: this is the max row number of the sheet, so it should be count -1
MaxRow uint16 MaxRow uint16
id int
parsed bool parsed bool
} }
@ -36,15 +48,14 @@ func (w *WorkSheet) Row(i int) *Row {
func (w *WorkSheet) parse(buf io.ReadSeeker) { func (w *WorkSheet) parse(buf io.ReadSeeker) {
w.rows = make(map[uint16]*Row) w.rows = make(map[uint16]*Row)
b := new(bof) b := new(bof)
var bof_pre *bof var bp *bof
for { for {
if err := binary.Read(buf, binary.LittleEndian, b); err == nil { if err := binary.Read(buf, binary.LittleEndian, b); err == nil {
bof_pre = w.parseBof(buf, b, bof_pre) bp = w.parseBof(buf, b, bp)
if b.Id == 0xa { if b.Id == 0xa {
break break
} }
} else { } else {
fmt.Println(err)
break break
} }
} }
@ -63,21 +74,17 @@ func (w *WorkSheet) parseBof(buf io.ReadSeeker, b *bof, pre *bof) *bof {
case 0x0BD: //MULRK case 0x0BD: //MULRK
mc := new(MulrkCol) mc := new(MulrkCol)
size := (b.Size - 6) / 6 size := (b.Size - 6) / 6
binary.Read(buf, binary.LittleEndian, &mc.Col)
mc.Xfrks = make([]XfRk, size) mc.Xfrks = make([]XfRk, size)
for i := uint16(0); i < size; i++ { binary.Read(buf, binary.LittleEndian, &mc.Col)
binary.Read(buf, binary.LittleEndian, &mc.Xfrks[i]) binary.Read(buf, binary.LittleEndian, &mc.Xfrks)
}
binary.Read(buf, binary.LittleEndian, &mc.LastColB) binary.Read(buf, binary.LittleEndian, &mc.LastColB)
col = mc col = mc
case 0x0BE: //MULBLANK case 0x0BE: //MULBLANK
mc := new(MulBlankCol) mc := new(MulBlankCol)
size := (b.Size - 6) / 2 size := (b.Size - 6) / 2
binary.Read(buf, binary.LittleEndian, &mc.Col)
mc.Xfs = make([]uint16, size) mc.Xfs = make([]uint16, size)
for i := uint16(0); i < size; i++ { binary.Read(buf, binary.LittleEndian, &mc.Col)
binary.Read(buf, binary.LittleEndian, &mc.Xfs[i]) binary.Read(buf, binary.LittleEndian, &mc.Xfs)
}
binary.Read(buf, binary.LittleEndian, &mc.LastColB) binary.Read(buf, binary.LittleEndian, &mc.LastColB)
col = mc col = mc
case 0x203: //NUMBER case 0x203: //NUMBER
@ -85,10 +92,26 @@ func (w *WorkSheet) parseBof(buf io.ReadSeeker, b *bof, pre *bof) *bof {
binary.Read(buf, binary.LittleEndian, col) binary.Read(buf, binary.LittleEndian, col)
case 0x06: //FORMULA case 0x06: //FORMULA
c := new(FormulaCol) c := new(FormulaCol)
binary.Read(buf, binary.LittleEndian, &c.Header) c.ws = w.id
c.Header = new(FormulaColHeader)
c.Bts = make([]byte, b.Size-20) c.Bts = make([]byte, b.Size-20)
binary.Read(buf, binary.LittleEndian, c.Header)
binary.Read(buf, binary.LittleEndian, &c.Bts) binary.Read(buf, binary.LittleEndian, &c.Bts)
col = c col = c
c.parse(w.wb, false)
if TYPE_STRING == c.vType {
binary.Read(buf, binary.LittleEndian, &c.Code)
binary.Read(buf, binary.LittleEndian, &c.Btl)
binary.Read(buf, binary.LittleEndian, &c.Btc)
var fms, fme = w.wb.parseString(buf, c.Btc)
if nil == fme {
c.value = fms
}
buf.Seek(-int64(c.Btl+4), 1)
}
case 0x27e: //RK case 0x27e: //RK
col = new(RkCol) col = new(RkCol)
binary.Read(buf, binary.LittleEndian, col) binary.Read(buf, binary.LittleEndian, col)
@ -100,18 +123,18 @@ func (w *WorkSheet) parseBof(buf io.ReadSeeker, b *bof, pre *bof) *bof {
binary.Read(buf, binary.LittleEndian, &c.BlankCol) binary.Read(buf, binary.LittleEndian, &c.BlankCol)
var count uint16 var count uint16
binary.Read(buf, binary.LittleEndian, &count) binary.Read(buf, binary.LittleEndian, &count)
c.Str, _ = w.wb.get_string(buf, count) c.Str, _ = w.wb.parseString(buf, count)
col = c col = c
case 0x201: //BLANK case 0x201: //BLANK
col = new(BlankCol) col = new(BlankCol)
binary.Read(buf, binary.LittleEndian, col) binary.Read(buf, binary.LittleEndian, col)
case 0x1b8: //HYPERLINK case 0x1b8: //HYPERLINK
var flag uint32
var count uint32
var hy HyperLink var hy HyperLink
binary.Read(buf, binary.LittleEndian, &hy.CellRange) binary.Read(buf, binary.LittleEndian, &hy.CellRange)
buf.Seek(20, 1) buf.Seek(20, 1)
var flag uint32
binary.Read(buf, binary.LittleEndian, &flag) binary.Read(buf, binary.LittleEndian, &flag)
var count uint32
if flag&0x14 != 0 { if flag&0x14 != 0 {
binary.Read(buf, binary.LittleEndian, &count) binary.Read(buf, binary.LittleEndian, &count)
@ -172,7 +195,6 @@ func (w *WorkSheet) add(content interface{}) {
w.addCell(col, ch) w.addCell(col, ch)
} }
} }
} }
func (w *WorkSheet) addCell(col Coler, ch contentHandler) { func (w *WorkSheet) addCell(col Coler, ch contentHandler) {
@ -180,33 +202,36 @@ func (w *WorkSheet) addCell(col Coler, ch contentHandler) {
} }
func (w *WorkSheet) addRange(rang Ranger, ch contentHandler) { func (w *WorkSheet) addRange(rang Ranger, ch contentHandler) {
for i := rang.FirstRow(); i <= rang.LastRow(); i++ { for i := rang.FirstRow(); i <= rang.LastRow(); i++ {
w.addContent(i, ch) w.addContent(i, ch)
} }
} }
func (w *WorkSheet) addContent(row_num uint16, ch contentHandler) { func (w *WorkSheet) addContent(num uint16, ch contentHandler) {
var row *Row var row *Row
var ok bool var ok bool
if row, ok = w.rows[row_num]; !ok { if row, ok = w.rows[num]; !ok {
info := new(rowInfo) info := new(rowInfo)
info.Index = row_num info.Index = num
row = w.addRow(info) row = w.addRow(info)
} }
row.cols[ch.FirstCol()] = ch row.cols[ch.FirstCol()] = ch
} }
func (w *WorkSheet) addRow(info *rowInfo) (row *Row) { func (w *WorkSheet) addRow(info *rowInfo) *Row {
var ok bool
var row *Row
if info.Index > w.MaxRow { if info.Index > w.MaxRow {
w.MaxRow = info.Index w.MaxRow = info.Index
} }
var ok bool
if row, ok = w.rows[info.Index]; ok { if row, ok = w.rows[info.Index]; ok {
row.info = info row.info = info
} else { } else {
row = &Row{info: info, cols: make(map[uint16]contentHandler)} row = &Row{info: info, cols: make(map[uint16]contentHandler, int(info.Last-info.First))}
w.rows[info.Index] = row w.rows[info.Index] = row
} }
return
return row
} }

12
xf.go
View File

@ -1,5 +1,9 @@
package xls package xls
type XF interface {
FormatNo() uint16
}
type Xf5 struct { type Xf5 struct {
Font uint16 Font uint16
Format uint16 Format uint16
@ -11,7 +15,7 @@ type Xf5 struct {
Linestyle uint16 Linestyle uint16
} }
func (x *Xf5) formatNo() uint16 { func (x *Xf5) FormatNo() uint16 {
return x.Format return x.Format
} }
@ -28,10 +32,6 @@ type Xf8 struct {
Groundcolor uint16 Groundcolor uint16
} }
func (x *Xf8) formatNo() uint16 { func (x *Xf8) FormatNo() uint16 {
return x.Format return x.Format
} }
type st_xf_data interface {
formatNo() uint16
}

15
xls.go
View File

@ -1,7 +1,9 @@
package xls package xls
import ( import (
"bytes"
"io" "io"
"io/ioutil"
"os" "os"
"github.com/extrame/ole2" "github.com/extrame/ole2"
@ -16,7 +18,16 @@ func Open(file string, charset string) (*WorkBook, error) {
} }
} }
//Open one xls file and return the closer //OpenWithBuffer open one xls file with memory buffer
func OpenWithBuffer(file string, charset string) (*WorkBook, error) {
if fi, err := ioutil.ReadFile(file); err == nil {
return OpenReader(bytes.NewReader(fi), charset)
} else {
return nil, err
}
}
//OpenWithCloser open one xls file and return the closer
func OpenWithCloser(file string, charset string) (*WorkBook, io.Closer, error) { func OpenWithCloser(file string, charset string) (*WorkBook, io.Closer, error) {
if fi, err := os.Open(file); err == nil { if fi, err := os.Open(file); err == nil {
wb, err := OpenReader(fi, charset) wb, err := OpenReader(fi, charset)
@ -26,7 +37,7 @@ func OpenWithCloser(file string, charset string) (*WorkBook, io.Closer, error) {
} }
} }
//Open xls file from reader //OpenReader open xls file from reader
func OpenReader(reader io.ReadSeeker, charset string) (wb *WorkBook, err error) { func OpenReader(reader io.ReadSeeker, charset string) (wb *WorkBook, err error) {
var ole *ole2.Ole var ole *ole2.Ole
if ole, err = ole2.Open(reader, charset); err == nil { if ole, err = ole2.Open(reader, charset); err == nil {