- Formatted cell data support, fix issue #48;

- Function `SetCellValue()` support `time.Time` data type parameter, relate issue #49;
- go doc and go test updated
This commit is contained in:
Ri Xu 2017-05-05 14:40:28 +08:00
parent 7f30a6c943
commit 8fbab47444
6 changed files with 357 additions and 13 deletions

24
cell.go
View File

@ -6,8 +6,10 @@ import (
"strings" "strings"
) )
// GetCellValue provides function to get value from cell by given sheet index // GetCellValue provides function to get formatted value from cell by given
// and axis in XLSX file. // sheet index and axis in XLSX file. If it is possible to apply a format to the
// cell value, it will do so, if not then an error will be returned, along with
// the raw value of the cell.
func (f *File) GetCellValue(sheet, axis string) string { func (f *File) GetCellValue(sheet, axis string) string {
xlsx := f.workSheetReader(sheet) xlsx := f.workSheetReader(sheet)
axis = strings.ToUpper(axis) axis = strings.ToUpper(axis)
@ -44,17 +46,29 @@ func (f *File) GetCellValue(sheet, axis string) string {
xlsxSI := 0 xlsxSI := 0
xlsxSI, _ = strconv.Atoi(r.V) xlsxSI, _ = strconv.Atoi(r.V)
xml.Unmarshal([]byte(f.readXML("xl/sharedStrings.xml")), &shardStrings) xml.Unmarshal([]byte(f.readXML("xl/sharedStrings.xml")), &shardStrings)
return shardStrings.SI[xlsxSI].T return f.formattedValue(r.S, shardStrings.SI[xlsxSI].T)
case "str": case "str":
return r.V return f.formattedValue(r.S, r.V)
default: default:
return r.V return f.formattedValue(r.S, r.V)
} }
} }
} }
return "" return ""
} }
// formattedValue provides function to returns a value after formatted. If it is
// possible to apply a format to the cell value, it will do so, if not then an
// error will be returned, along with the raw value of the cell.
func (f *File) formattedValue(s int, v string) string {
if s == 0 {
return v
}
var styleSheet xlsxStyleSheet
xml.Unmarshal([]byte(f.readXML("xl/styles.xml")), &styleSheet)
return builtInNumFmtFunc[styleSheet.CellXfs.Xf[s].NumFmtID](styleSheet.CellXfs.Xf[s].NumFmtID, v)
}
// GetCellFormula provides function to get formula from cell by given sheet // GetCellFormula provides function to get formula from cell by given sheet
// index and axis in XLSX file. // index and axis in XLSX file.
func (f *File) GetCellFormula(sheet, axis string) string { func (f *File) GetCellFormula(sheet, axis string) string {

119
date.go Normal file
View File

@ -0,0 +1,119 @@
package excelize
import (
"math"
"time"
)
// timeLocationUTC defined the UTC time location.
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) {
switch {
case -0.5 < julianFraction && julianFraction < 0.5:
julianFraction += 0.5
case julianFraction >= 0.5:
julianDays++
julianFraction -= 0.5
case julianFraction <= -0.5:
julianDays--
julianFraction += 1.5
}
return julianDays, julianFraction
}
// fractionOfADay provides function to return the integer values for hour,
// 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) {
const (
c1us = 1e3
c1s = 1e9
c1day = 24 * 60 * 60 * c1s
)
frac := int64(c1day*fraction + c1us/2)
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 {
part1I, part1F := math.Modf(part1)
part2I, part2F := math.Modf(part2)
julianDays := part1I + part2I
julianFraction := part1F + part2F
julianDays, julianFraction = shiftJulianToNoon(julianDays, julianFraction)
day, month, year := doTheFliegelAndVanFlandernAlgorithm(int(julianDays))
hours, minutes, seconds, nanoseconds := fractionOfADay(julianFraction)
return time.Date(year, time.Month(month), day, hours, minutes, seconds, nanoseconds, time.UTC)
}
// By this point generations of programmers have repeated the algorithm sent to
// the editor of "Communications of the ACM" in 1968 (published in CACM, volume
// 11, number 10, October 1968, p.657). None of those programmers seems to have
// found it necessary to explain the constants or variable names set out by
// Henry F. Fliegel and Thomas C. Van Flandern. Maybe one day I'll buy that
// jounal and expand an explanation here - that day is not today.
func doTheFliegelAndVanFlandernAlgorithm(jd int) (day, month, year int) {
l := jd + 68569
n := (4 * l) / 146097
l = l - (146097*n+3)/4
i := (4000 * (l + 1)) / 1461001
l = l - (1461*i)/4 + 31
j := (80 * l) / 2447
d := l - (2447*j)/80
l = j / 11
m := j + 2 - (12 * l)
y := 100*(n-49) + i + l
return d, m, y
}
// 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 {
var date time.Time
var intPart = int64(excelTime)
// Excel uses Julian dates prior to March 1st 1900, and Gregorian
// thereafter.
if intPart <= 61 {
const OFFSET1900 = 15018.0
const OFFSET1904 = 16480.0
const MJD0 float64 = 2400000.5
var date time.Time
if date1904 {
date = julianDateToGregorianTime(MJD0, excelTime+OFFSET1904)
} else {
date = julianDateToGregorianTime(MJD0, excelTime+OFFSET1900)
}
return date
}
var floatPart = excelTime - float64(intPart)
var dayNanoSeconds float64 = 24 * 60 * 60 * 1000 * 1000 * 1000
if date1904 {
date = time.Date(1904, 1, 1, 0, 0, 0, 0, time.UTC)
} else {
date = time.Date(1899, 12, 30, 0, 0, 0, 0, time.UTC)
}
durationDays := time.Duration(intPart) * time.Hour * 24
durationPart := time.Duration(dayNanoSeconds * floatPart)
return date.Add(durationDays).Add(durationPart)
}

View File

@ -4,11 +4,13 @@ import (
"archive/zip" "archive/zip"
"bytes" "bytes"
"encoding/xml" "encoding/xml"
"fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"os" "os"
"strconv" "strconv"
"strings" "strings"
"time"
) )
// File define a populated XLSX file struct. // File define a populated XLSX file struct.
@ -84,8 +86,13 @@ func (f *File) SetCellValue(sheet, axis string, value interface{}) {
f.SetCellStr(sheet, axis, t) f.SetCellStr(sheet, axis, t)
case []byte: case []byte:
f.SetCellStr(sheet, axis, string(t)) f.SetCellStr(sheet, axis, string(t))
default: case time.Time:
f.SetCellDefault(sheet, axis, strconv.FormatFloat(float64(timeToExcelTime(timeToUTCTime(value.(time.Time)))), 'f', -1, 32))
f.SetCellStyle(sheet, axis, axis, `{"number_format": 22}`)
case nil:
f.SetCellStr(sheet, axis, "") f.SetCellStr(sheet, axis, "")
default:
f.SetCellStr(sheet, axis, fmt.Sprintf("%v", value))
} }
} }

View File

@ -7,6 +7,7 @@ import (
"io/ioutil" "io/ioutil"
"strconv" "strconv"
"testing" "testing"
"time"
) )
func TestOpenFile(t *testing.T) { func TestOpenFile(t *testing.T) {
@ -66,6 +67,8 @@ func TestOpenFile(t *testing.T) {
xlsx.SetCellValue("Sheet2", "F2", float32(42)) xlsx.SetCellValue("Sheet2", "F2", float32(42))
xlsx.SetCellValue("Sheet2", "F2", float64(42)) xlsx.SetCellValue("Sheet2", "F2", float64(42))
xlsx.SetCellValue("Sheet2", "G2", nil) xlsx.SetCellValue("Sheet2", "G2", nil)
xlsx.SetCellValue("Sheet2", "G3", uint8(8))
xlsx.SetCellValue("Sheet2", "G4", time.Now())
// Test completion column. // Test completion column.
xlsx.SetCellValue("Sheet2", "M2", nil) xlsx.SetCellValue("Sheet2", "M2", nil)
// Test read cell value with given axis large than exists row. // Test read cell value with given axis large than exists row.
@ -322,18 +325,40 @@ func TestSetCellStyleBorder(t *testing.T) {
} }
} }
func TestSetCellStyleFill(t *testing.T) { func TestSetCellStyleNumberFormat(t *testing.T) {
xlsx, err := OpenFile("./test/Workbook_2.xlsx") xlsx, err := OpenFile("./test/Workbook_2.xlsx")
if err != nil { if err != nil {
t.Log(err) t.Log(err)
} }
xlsx.SetCellValue("Sheet1", "N23", 42920.5)
// Test only set fill and number format for a cell. // Test only set fill and number format for a cell.
err = xlsx.SetCellStyle("Sheet1", "N23", "N23", `{"fill":{"type":"gradient","color":["#FFFFFF","#E0EBF5"],"shading":4},"number_format":22}`) col := []string{"L", "M", "N", "O", "P"}
data := []int{0, 1, 2, 3, 4, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49}
value := []string{"37947.7500001", "-37947.7500001", "0.007", "2.1", "String"}
for i, v := range value {
for k, d := range data {
c := col[i] + strconv.Itoa(k+1)
var val float64
val, err = strconv.ParseFloat(v, 64)
if err != nil {
xlsx.SetCellValue("Sheet2", c, v)
} else {
xlsx.SetCellValue("Sheet2", c, val)
}
err := xlsx.SetCellStyle("Sheet2", c, c, `{"fill":{"type":"gradient","color":["#FFFFFF","#E0EBF5"],"shading":5},"number_format": `+strconv.Itoa(d)+`}`)
if err != nil { if err != nil {
t.Log(err) t.Log(err)
} }
err = xlsx.SetCellStyle("Sheet1", "N24", "N24", `{"fill":{"type":"gradient","color":["#FFFFFF","#E0EBF5"],"shading":5},"number_format":23}`) t.Log(xlsx.GetCellValue("Sheet2", c))
}
}
err = xlsx.Save()
if err != nil {
t.Log(err)
}
}
func TestSetCellStyleFill(t *testing.T) {
xlsx, err := OpenFile("./test/Workbook_2.xlsx")
if err != nil { if err != nil {
t.Log(err) t.Log(err)
} }

View File

@ -141,10 +141,10 @@ func (xlsx *xlsxC) getValueFrom(f *File, d *xlsxSST) (string, error) {
} }
return value, nil return value, nil
} }
return d.SI[xlsxSI].T, nil return f.formattedValue(xlsx.S, d.SI[xlsxSI].T), nil
case "str": case "str":
return xlsx.V, nil return f.formattedValue(xlsx.S, xlsx.V), nil
default: default:
return xlsx.V, nil return f.formattedValue(xlsx.S, xlsx.V), nil
} }
} }

179
styles.go
View File

@ -3,6 +3,8 @@ package excelize
import ( import (
"encoding/json" "encoding/json"
"encoding/xml" "encoding/xml"
"fmt"
"math"
"strconv" "strconv"
"strings" "strings"
) )
@ -45,6 +47,183 @@ var builtInNumFmt = map[int]string{
49: "@", 49: "@",
} }
// builtInNumFmtFunc defined the format conversion functions map. Partial format
// code doesn't support currently and will return original string.
var builtInNumFmtFunc = map[int]func(i int, v string) string{
0: formatToString,
1: formatToInt,
2: formatToFloat,
3: formatToInt,
4: formatToFloat,
9: formatToC,
10: formatToD,
11: formatToE,
12: formatToString, // Doesn't support currently
13: formatToString, // Doesn't support currently
14: parseTime,
15: parseTime,
16: parseTime,
17: parseTime,
18: parseTime,
19: parseTime,
20: parseTime,
21: parseTime,
22: parseTime,
37: formatToA,
38: formatToA,
39: formatToB,
40: formatToB,
41: formatToString, // Doesn't support currently
42: formatToString, // Doesn't support currently
43: formatToString, // Doesn't support currently
44: formatToString, // Doesn't support currently
45: parseTime,
46: parseTime,
47: parseTime,
48: formatToE,
49: formatToString,
}
// formatToString provides function to return original string by given built-in
// number formats code and cell string.
func formatToString(i int, v string) string {
return v
}
// formatToInt provides function to convert original string to integer format as
// string type by given built-in number formats code and cell string.
func formatToInt(i int, v string) string {
f, err := strconv.ParseFloat(v, 64)
if err != nil {
return v
}
return fmt.Sprintf("%d", int(f))
}
// formatToFloat provides function to convert original string to float format as
// string type by given built-in number formats code and cell string.
func formatToFloat(i int, v string) string {
f, err := strconv.ParseFloat(v, 64)
if err != nil {
return v
}
return fmt.Sprintf("%.2f", f)
}
// formatToA provides function to convert original string to special format as
// string type by given built-in number formats code and cell string.
func formatToA(i int, v string) string {
f, err := strconv.ParseFloat(v, 64)
if err != nil {
return v
}
if f < 0 {
t := int(math.Abs(f))
return fmt.Sprintf("(%d)", t)
}
t := int(f)
return fmt.Sprintf("%d", t)
}
// formatToB provides function to convert original string to special format as
// string type by given built-in number formats code and cell string.
func formatToB(i int, v string) string {
f, err := strconv.ParseFloat(v, 64)
if err != nil {
return v
}
if f < 0 {
return fmt.Sprintf("(%.2f)", f)
}
return fmt.Sprintf("%.2f", f)
}
// formatToC provides function to convert original string to special format as
// string type by given built-in number formats code and cell string.
func formatToC(i int, v string) string {
f, err := strconv.ParseFloat(v, 64)
if err != nil {
return v
}
f = f * 100
return fmt.Sprintf("%d%%", int(f))
}
// formatToD provides function to convert original string to special format as
// string type by given built-in number formats code and cell string.
func formatToD(i int, v string) string {
f, err := strconv.ParseFloat(v, 64)
if err != nil {
return v
}
f = f * 100
return fmt.Sprintf("%.2f%%", f)
}
// formatToE provides function to convert original string to special format as
// string type by given built-in number formats code and cell string.
func formatToE(i int, v string) string {
f, err := strconv.ParseFloat(v, 64)
if err != nil {
return v
}
return fmt.Sprintf("%.e", f)
}
// 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.
func parseTime(i int, v string) string {
f, err := strconv.ParseFloat(v, 64)
if err != nil {
return v
}
val := timeFromExcelTime(f, false)
format := builtInNumFmt[i]
replacements := []struct{ xltime, gotime string }{
{"yyyy", "2006"},
{"yy", "06"},
{"mmmm", "%%%%"},
{"dddd", "&&&&"},
{"dd", "02"},
{"d", "2"},
{"mmm", "Jan"},
{"mmss", "0405"},
{"ss", "05"},
{"hh", "15"},
{"h", "3"},
{"mm:", "04:"},
{":mm", ":04"},
{"mm", "01"},
{"am/pm", "pm"},
{"m/", "1/"},
{"%%%%", "January"},
{"&&&&", "Monday"},
}
for _, repl := range replacements {
format = strings.Replace(format, repl.xltime, repl.gotime, 1)
}
// If the hour is optional, strip it out, along with the possible dangling
// colon that would remain.
if val.Hour() < 1 {
format = strings.Replace(format, "]:", "]", 1)
format = strings.Replace(format, "[3]", "", 1)
format = strings.Replace(format, "[15]", "", 1)
} else {
format = strings.Replace(format, "[3]", "3", 1)
format = strings.Replace(format, "[15]", "15", 1)
}
return val.Format(format)
}
// parseFormatStyleSet provides function to parse the format settings of the // parseFormatStyleSet provides function to parse the format settings of the
// borders. // borders.
func parseFormatStyleSet(style string) (*formatCellStyle, error) { func parseFormatStyleSet(style string) (*formatCellStyle, error) {