try to use a new formatter for date and decimal
This commit is contained in:
parent
ce73093973
commit
d3f14f70d8
8
col.go
8
col.go
@ -7,7 +7,7 @@ import (
|
|||||||
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/extrame/goyymmdd"
|
internalFmt "github.com/extrame/xls/format"
|
||||||
)
|
)
|
||||||
|
|
||||||
//content type
|
//content type
|
||||||
@ -57,8 +57,8 @@ func (xf *XfRk) String(wb *WorkBook) string {
|
|||||||
if !isFloat {
|
if !isFloat {
|
||||||
f = float64(i)
|
f = float64(i)
|
||||||
}
|
}
|
||||||
t := timeFromExcelTime(f, wb.dateMode == 1)
|
// t := timeFromExcelTime(f, wb.dateMode == 1)
|
||||||
return yymmdd.Format(t, formatter.str)
|
return formatter.Format(f, wb.dateMode == 1)
|
||||||
}
|
}
|
||||||
// see http://www.openoffice.org/sc/excelfileformat.pdf Page #174
|
// 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
|
} else if 14 <= fNo && fNo <= 17 || fNo == 22 || 27 <= fNo && fNo <= 36 || 50 <= fNo && fNo <= 58 { // jp. date format
|
||||||
@ -66,7 +66,7 @@ func (xf *XfRk) String(wb *WorkBook) string {
|
|||||||
if !isFloat {
|
if !isFloat {
|
||||||
f = float64(i)
|
f = float64(i)
|
||||||
}
|
}
|
||||||
t := timeFromExcelTime(f, wb.dateMode == 1)
|
t := internalFmt.TimeFromExcelTime(f, wb.dateMode == 1)
|
||||||
return t.Format(time.RFC3339) //TODO it should be international
|
return t.Format(time.RFC3339) //TODO it should be international
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
15
format.go
15
format.go
@ -1,9 +1,22 @@
|
|||||||
package xls
|
package xls
|
||||||
|
|
||||||
type Format struct {
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
formatter "github.com/extrame/xls/format"
|
||||||
|
)
|
||||||
|
|
||||||
|
type format struct {
|
||||||
Head struct {
|
Head struct {
|
||||||
Index uint16
|
Index uint16
|
||||||
Size uint16
|
Size uint16
|
||||||
}
|
}
|
||||||
str string
|
str string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *format) Format(val float64, date1904 bool) string {
|
||||||
|
_, tokens := formatter.Lexer(f.str)
|
||||||
|
ds := formatter.Parse(tokens)
|
||||||
|
fmt.Println("=>", val)
|
||||||
|
return ds.Format(val, date1904)
|
||||||
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package xls
|
package format
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
@ -69,7 +69,7 @@ func doTheFliegelAndVanFlandernAlgorithm(jd int) (day, month, year int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Convert an excelTime representation (stored as a floating point number) to a time.Time.
|
// 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 = int64(excelTime)
|
||||||
// Excel uses Julian dates prior to March 1st 1900, and
|
// Excel uses Julian dates prior to March 1st 1900, and
|
106
format/formatter.go
Normal file
106
format/formatter.go
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
package format
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
var InvalidTimeFormat = errors.New("invalid time format")
|
||||||
|
|
||||||
|
type Formatter struct {
|
||||||
|
typ int
|
||||||
|
Items []ItemFormatter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Formatter) Format(val float64, date1904 bool) string {
|
||||||
|
var gf string
|
||||||
|
for _, i := range f.Items {
|
||||||
|
itemFormatString, _ := i.translateToGolangFormat()
|
||||||
|
gf += itemFormatString
|
||||||
|
}
|
||||||
|
fmt.Println(f)
|
||||||
|
if f.typ == DATEFORMAT {
|
||||||
|
t := TimeFromExcelTime(val, date1904)
|
||||||
|
return t.Format(gf)
|
||||||
|
} else {
|
||||||
|
return fmt.Sprintf("%f", val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ItemFormatter interface {
|
||||||
|
translateToGolangFormat() (string, error)
|
||||||
|
setOriginal(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
type basicFormatter struct {
|
||||||
|
origin string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *basicFormatter) translateToGolangFormat() (string, error) {
|
||||||
|
return self.origin, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *basicFormatter) setOriginal(o string) {
|
||||||
|
self.origin = o
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *basicFormatter) String() string {
|
||||||
|
return fmt.Sprintf("basic formatter as (%s)", self.origin)
|
||||||
|
}
|
||||||
|
|
||||||
|
type YearFormatter struct {
|
||||||
|
basicFormatter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (y *YearFormatter) translateToGolangFormat() (string, error) {
|
||||||
|
switch len(y.origin) {
|
||||||
|
case 4:
|
||||||
|
return "2006", nil
|
||||||
|
case 2:
|
||||||
|
return "06", nil
|
||||||
|
default:
|
||||||
|
return "", InvalidTimeFormat
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (y *YearFormatter) String() string {
|
||||||
|
return fmt.Sprintf("year formatter as (%s)", y.origin)
|
||||||
|
}
|
||||||
|
|
||||||
|
type MonthFormatter struct {
|
||||||
|
basicFormatter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *MonthFormatter) translateToGolangFormat() (string, error) {
|
||||||
|
switch len(self.origin) {
|
||||||
|
case 2:
|
||||||
|
return "01", nil
|
||||||
|
case 1:
|
||||||
|
return "1", nil
|
||||||
|
default:
|
||||||
|
return "", InvalidTimeFormat
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *MonthFormatter) String() string {
|
||||||
|
return fmt.Sprintf("month formatter as (%s)", self.origin)
|
||||||
|
}
|
||||||
|
|
||||||
|
type DayFormatter struct {
|
||||||
|
basicFormatter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *DayFormatter) translateToGolangFormat() (string, error) {
|
||||||
|
switch len(self.origin) {
|
||||||
|
case 2:
|
||||||
|
return "02", nil
|
||||||
|
case 1:
|
||||||
|
return "2", nil
|
||||||
|
default:
|
||||||
|
return "", InvalidTimeFormat
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *DayFormatter) String() string {
|
||||||
|
return fmt.Sprintf("day formatter as (%s)", self.origin)
|
||||||
|
}
|
13
format/lang.go
Normal file
13
format/lang.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package format
|
||||||
|
|
||||||
|
const T_YEAR_MARK string = "T_YEAR_MARK"
|
||||||
|
const T_MONTH_MARK string = "T_MONTH_MARK"
|
||||||
|
const T_DAY_MARK string = "T_DAY_MARK"
|
||||||
|
|
||||||
|
const T_RAW_MARK string = "T_RAW_MARK"
|
||||||
|
|
||||||
|
const T_EOF string = "T_EOF"
|
||||||
|
|
||||||
|
const T_STRING_MARK string = "T_STRING_MARK"
|
||||||
|
const T_DECIMAL_MARK string = "T_DECIMAL_MARK"
|
||||||
|
const T_COMMA_MARK string = "T_COMMA_MARK"
|
150
format/lexer.go
Normal file
150
format/lexer.go
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
package format
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DATEFORMAT = iota
|
||||||
|
DECIMALFORMAT = iota
|
||||||
|
)
|
||||||
|
|
||||||
|
// LexToken holds is a (type, value) array.
|
||||||
|
type LexToken [3]string
|
||||||
|
|
||||||
|
// EOF character
|
||||||
|
var EOF string = "+++EOF+++"
|
||||||
|
|
||||||
|
// lexerState represents the state of the scanner
|
||||||
|
// as a function that returns the next state.
|
||||||
|
type lexerState func(*lexer) lexerState
|
||||||
|
|
||||||
|
// run lexes the input by executing state functions until
|
||||||
|
// the state is nil.
|
||||||
|
func (l *lexer) Run() {
|
||||||
|
for state := l.initialState; state != nil; {
|
||||||
|
state = state(l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lexer creates a new scanner for the input string.
|
||||||
|
func Lexer(input string) (*lexer, []LexToken) {
|
||||||
|
l := &lexer{
|
||||||
|
input: input,
|
||||||
|
tokens: make([]LexToken, 0),
|
||||||
|
lineno: 1,
|
||||||
|
}
|
||||||
|
l.initialState = initLexerState
|
||||||
|
l.Run()
|
||||||
|
return l, l.tokens
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexer holds the state of the scanner.
|
||||||
|
type lexer struct {
|
||||||
|
input string // the string being scanned.
|
||||||
|
start int // start position of this item.
|
||||||
|
pos int // current position in the input.
|
||||||
|
width int // width of last rune read from input.
|
||||||
|
tokens []LexToken // scanned items.
|
||||||
|
initialState lexerState
|
||||||
|
typ int
|
||||||
|
lineno int
|
||||||
|
}
|
||||||
|
|
||||||
|
// next returns the next rune in the input.
|
||||||
|
func (l *lexer) next() string {
|
||||||
|
var r rune
|
||||||
|
if l.pos >= len(l.input) {
|
||||||
|
l.width = 0
|
||||||
|
return EOF
|
||||||
|
}
|
||||||
|
r, l.width = utf8.DecodeRuneInString(l.input[l.pos:])
|
||||||
|
l.pos += l.width
|
||||||
|
return string(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore skips over the pending input before this point.
|
||||||
|
func (l *lexer) ignore() {
|
||||||
|
l.start = l.pos
|
||||||
|
}
|
||||||
|
|
||||||
|
// backup steps back one rune.
|
||||||
|
// Can be called only once per call of next.
|
||||||
|
func (l *lexer) backup() {
|
||||||
|
l.pos -= l.width
|
||||||
|
}
|
||||||
|
|
||||||
|
// acceptRun consumes a run of runes from the valid set.
|
||||||
|
func (l *lexer) acceptRun(valid string) {
|
||||||
|
for strings.Index(valid, l.next()) >= 0 {
|
||||||
|
}
|
||||||
|
l.backup()
|
||||||
|
}
|
||||||
|
|
||||||
|
// acceptRun consumes a run of runes from the valid set.
|
||||||
|
func (l *lexer) acceptUntil(marker string) {
|
||||||
|
for r := l.next(); r != EOF && strings.Index(marker, r) < 0; r = l.next() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// emit passes an item back to the client.
|
||||||
|
func (l *lexer) emit(t string) {
|
||||||
|
l.tokens = append(l.tokens, LexToken{t, l.input[l.start:l.pos], strconv.Itoa(l.lineno)})
|
||||||
|
l.start = l.pos
|
||||||
|
}
|
||||||
|
|
||||||
|
// emit passes an item back to the client.
|
||||||
|
func (l *lexer) emitRaw() {
|
||||||
|
if l.pos-l.start > 1 {
|
||||||
|
l.tokens = append(l.tokens, LexToken{T_RAW_MARK, l.input[l.start : l.pos-1], strconv.Itoa(l.lineno)})
|
||||||
|
l.start = l.pos - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// emit passes an item back to the client.
|
||||||
|
func (l *lexer) emitWithoutEnd(t string) {
|
||||||
|
if l.pos-l.start > 1 {
|
||||||
|
l.tokens = append(l.tokens, LexToken{t, l.input[l.start : l.pos-1], strconv.Itoa(l.lineno)})
|
||||||
|
l.start = l.pos
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialState is the starting point for the
|
||||||
|
// scanner. It scans through each character and decides
|
||||||
|
// which state to create for the lexer. lexerState == nil
|
||||||
|
// is exit scanner.
|
||||||
|
func initLexerState(l *lexer) lexerState {
|
||||||
|
for r := l.next(); r != EOF; r = l.next() {
|
||||||
|
if r == "y" {
|
||||||
|
l.emitRaw()
|
||||||
|
l.acceptRun("y")
|
||||||
|
l.emit(T_YEAR_MARK)
|
||||||
|
} else if r == "m" {
|
||||||
|
l.emitRaw()
|
||||||
|
l.acceptRun("m")
|
||||||
|
l.emit(T_MONTH_MARK)
|
||||||
|
} else if r == "d" {
|
||||||
|
l.emitRaw()
|
||||||
|
l.acceptRun("d")
|
||||||
|
l.emit(T_DAY_MARK)
|
||||||
|
} else if r == "\"" {
|
||||||
|
l.emitRaw()
|
||||||
|
l.ignore()
|
||||||
|
l.acceptUntil("\"")
|
||||||
|
l.emitWithoutEnd(T_STRING_MARK)
|
||||||
|
} else if r == "#" {
|
||||||
|
l.emitRaw()
|
||||||
|
l.acceptRun("#,")
|
||||||
|
l.emit(T_COMMA_MARK)
|
||||||
|
} else if r == "0" {
|
||||||
|
l.emitRaw()
|
||||||
|
l.acceptRun("0123456789.")
|
||||||
|
l.emit(T_DECIMAL_MARK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
l.emit(T_EOF)
|
||||||
|
return nil
|
||||||
|
}
|
36
format/parse_test.go
Normal file
36
format/parse_test.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package format
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseYY(t *testing.T) {
|
||||||
|
_, tokens := Lexer(`yy-`)
|
||||||
|
ds := Parse(tokens)
|
||||||
|
fmt.Println(ds.Format(8100, true))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMM(t *testing.T) {
|
||||||
|
_, tokens := Lexer(`yyyymm`)
|
||||||
|
ds := Parse(tokens)
|
||||||
|
fmt.Println(ds.Format(8100, true))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMM2(t *testing.T) {
|
||||||
|
_, tokens := Lexer(`yyyy-mm"fasd65af"----`)
|
||||||
|
ds := Parse(tokens)
|
||||||
|
fmt.Println(ds.Format(8800, false))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseDD(t *testing.T) {
|
||||||
|
_, tokens := Lexer(`yyyy-mm-dd`)
|
||||||
|
ds := Parse(tokens)
|
||||||
|
fmt.Println(ds.Format(8100, true))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseNum(t *testing.T) {
|
||||||
|
_, tokens := Lexer(`"$"#,##0.00`)
|
||||||
|
ds := Parse(tokens)
|
||||||
|
fmt.Println(ds.Format(8800, false))
|
||||||
|
}
|
78
format/parser.go
Normal file
78
format/parser.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package format
|
||||||
|
|
||||||
|
// Parse creates a new parser with the recommended
|
||||||
|
// parameters.
|
||||||
|
func Parse(tokens []LexToken) Formatter {
|
||||||
|
p := &parser{
|
||||||
|
tokens: tokens,
|
||||||
|
pos: -1,
|
||||||
|
}
|
||||||
|
p.initState = initialParserState
|
||||||
|
return p.run()
|
||||||
|
}
|
||||||
|
|
||||||
|
// run starts the statemachine
|
||||||
|
func (p *parser) run() Formatter {
|
||||||
|
var f Formatter
|
||||||
|
for state := p.initState; state != nil; {
|
||||||
|
state = state(p, &f)
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// parserState represents the state of the scanner
|
||||||
|
// as a function that returns the next state.
|
||||||
|
type parserState func(*parser, *Formatter) parserState
|
||||||
|
|
||||||
|
// nest returns what the next token AND
|
||||||
|
// advances p.pos.
|
||||||
|
func (p *parser) next() *LexToken {
|
||||||
|
if p.pos >= len(p.tokens)-1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
p.pos += 1
|
||||||
|
return &p.tokens[p.pos]
|
||||||
|
}
|
||||||
|
|
||||||
|
// the parser type
|
||||||
|
type parser struct {
|
||||||
|
tokens []LexToken
|
||||||
|
pos int
|
||||||
|
serial int
|
||||||
|
|
||||||
|
initState parserState
|
||||||
|
}
|
||||||
|
|
||||||
|
// the starting state for parsing
|
||||||
|
func initialParserState(p *parser, f *Formatter) parserState {
|
||||||
|
var t *LexToken
|
||||||
|
for t = p.next(); t[0] != T_EOF; t = p.next() {
|
||||||
|
var item ItemFormatter
|
||||||
|
switch t[0] {
|
||||||
|
case T_YEAR_MARK:
|
||||||
|
f.typ = DATEFORMAT
|
||||||
|
item = new(YearFormatter)
|
||||||
|
case T_MONTH_MARK:
|
||||||
|
f.typ = DATEFORMAT
|
||||||
|
item = new(MonthFormatter)
|
||||||
|
case T_DAY_MARK:
|
||||||
|
f.typ = DATEFORMAT
|
||||||
|
item = new(DayFormatter)
|
||||||
|
case T_RAW_MARK:
|
||||||
|
item = new(basicFormatter)
|
||||||
|
case T_STRING_MARK:
|
||||||
|
item = new(basicFormatter)
|
||||||
|
case T_COMMA_MARK, T_DECIMAL_MARK:
|
||||||
|
f.typ = DECIMALFORMAT
|
||||||
|
item = new(basicFormatter)
|
||||||
|
}
|
||||||
|
item.setOriginal(t[1])
|
||||||
|
f.Items = append(f.Items, item)
|
||||||
|
}
|
||||||
|
if len(t[1]) > 0 {
|
||||||
|
r := new(basicFormatter)
|
||||||
|
r.origin = t[1]
|
||||||
|
f.Items = append(f.Items, r)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
10
workbook.go
10
workbook.go
@ -15,7 +15,7 @@ type WorkBook struct {
|
|||||||
Codepage uint16
|
Codepage uint16
|
||||||
Xfs []st_xf_data
|
Xfs []st_xf_data
|
||||||
Fonts []Font
|
Fonts []Font
|
||||||
Formats map[uint16]*Format
|
Formats map[uint16]*format
|
||||||
//All the sheets from the workbook
|
//All the sheets from the workbook
|
||||||
sheets []*WorkSheet
|
sheets []*WorkSheet
|
||||||
Author string
|
Author string
|
||||||
@ -30,7 +30,7 @@ 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)
|
wb := new(WorkBook)
|
||||||
wb.Formats = make(map[uint16]*Format)
|
wb.Formats = make(map[uint16]*format)
|
||||||
// wb.bts = bts
|
// wb.bts = bts
|
||||||
wb.rs = rs
|
wb.rs = rs
|
||||||
wb.sheets = make([]*WorkSheet, 0)
|
wb.sheets = make([]*WorkSheet, 0)
|
||||||
@ -61,11 +61,11 @@ func (w *WorkBook) addFont(font *FontInfo, buf io.ReadSeeker) {
|
|||||||
w.Fonts = append(w.Fonts, Font{Info: font, Name: name})
|
w.Fonts = append(w.Fonts, Font{Info: font, Name: name})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *WorkBook) addFormat(format *Format) {
|
func (w *WorkBook) addFormat(fmt *format) {
|
||||||
if w.Formats == nil {
|
if w.Formats == nil {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
w.Formats[format.Head.Index] = format
|
w.Formats[fmt.Head.Index] = fmt
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
@ -147,7 +147,7 @@ func (wb *WorkBook) parseBof(buf io.ReadSeeker, b *bof, pre *bof, offset_pre int
|
|||||||
binary.Read(buf_item, binary.LittleEndian, f)
|
binary.Read(buf_item, binary.LittleEndian, f)
|
||||||
wb.addFont(f, buf_item)
|
wb.addFont(f, buf_item)
|
||||||
case 0x41E: //FORMAT
|
case 0x41E: //FORMAT
|
||||||
font := new(Format)
|
font := new(format)
|
||||||
binary.Read(buf_item, binary.LittleEndian, &font.Head)
|
binary.Read(buf_item, binary.LittleEndian, &font.Head)
|
||||||
font.str, _ = wb.get_string(buf_item, font.Head.Size)
|
font.str, _ = wb.get_string(buf_item, font.Head.Size)
|
||||||
wb.addFormat(font)
|
wb.addFormat(font)
|
||||||
|
Loading…
Reference in New Issue
Block a user