diff --git a/col.go b/col.go index 371af91..66171c8 100644 --- a/col.go +++ b/col.go @@ -7,7 +7,7 @@ import ( "time" - "github.com/extrame/goyymmdd" + internalFmt "github.com/extrame/xls/format" ) //content type @@ -57,8 +57,8 @@ func (xf *XfRk) String(wb *WorkBook) string { if !isFloat { f = float64(i) } - t := timeFromExcelTime(f, wb.dateMode == 1) - return yymmdd.Format(t, formatter.str) + // t := timeFromExcelTime(f, wb.dateMode == 1) + return formatter.Format(f, wb.dateMode == 1) } // 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 @@ -66,7 +66,7 @@ func (xf *XfRk) String(wb *WorkBook) string { if !isFloat { 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 } } diff --git a/format.go b/format.go index 35b576c..3fc159f 100644 --- a/format.go +++ b/format.go @@ -1,9 +1,22 @@ package xls -type Format struct { +import ( + "fmt" + + formatter "github.com/extrame/xls/format" +) + +type format struct { Head struct { Index uint16 Size uint16 } 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) +} diff --git a/date.go b/format/date.go similarity index 97% rename from date.go rename to format/date.go index b7d2d04..90d15e7 100644 --- a/date.go +++ b/format/date.go @@ -1,4 +1,4 @@ -package xls +package format import ( "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. -func timeFromExcelTime(excelTime float64, date1904 bool) time.Time { +func TimeFromExcelTime(excelTime float64, date1904 bool) time.Time { var date time.Time var intPart int64 = int64(excelTime) // Excel uses Julian dates prior to March 1st 1900, and diff --git a/format/formatter.go b/format/formatter.go new file mode 100644 index 0000000..1eaa926 --- /dev/null +++ b/format/formatter.go @@ -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) +} diff --git a/format/lang.go b/format/lang.go new file mode 100644 index 0000000..68c9e3b --- /dev/null +++ b/format/lang.go @@ -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" diff --git a/format/lexer.go b/format/lexer.go new file mode 100644 index 0000000..a22f74d --- /dev/null +++ b/format/lexer.go @@ -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 +} diff --git a/format/parse_test.go b/format/parse_test.go new file mode 100644 index 0000000..cd536e4 --- /dev/null +++ b/format/parse_test.go @@ -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)) +} diff --git a/format/parser.go b/format/parser.go new file mode 100644 index 0000000..e10f70f --- /dev/null +++ b/format/parser.go @@ -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 +} diff --git a/workbook.go b/workbook.go index e8199b7..b203432 100644 --- a/workbook.go +++ b/workbook.go @@ -15,7 +15,7 @@ type WorkBook struct { Codepage uint16 Xfs []st_xf_data Fonts []Font - Formats map[uint16]*Format + Formats map[uint16]*format //All the sheets from the workbook sheets []*WorkSheet Author string @@ -30,7 +30,7 @@ type WorkBook struct { //read workbook from ole2 file func newWorkBookFromOle2(rs io.ReadSeeker) *WorkBook { wb := new(WorkBook) - wb.Formats = make(map[uint16]*Format) + wb.Formats = make(map[uint16]*format) // wb.bts = bts wb.rs = rs 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}) } -func (w *WorkBook) addFormat(format *Format) { +func (w *WorkBook) addFormat(fmt *format) { if w.Formats == nil { 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) { @@ -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) wb.addFont(f, buf_item) case 0x41E: //FORMAT - font := new(Format) + font := new(format) binary.Read(buf_item, binary.LittleEndian, &font.Head) font.str, _ = wb.get_string(buf_item, font.Head.Size) wb.addFormat(font)