From 10ff2752d9e98c0a99c3f885dbfa0e364d25de62 Mon Sep 17 00:00:00 2001 From: Ivan Date: Sun, 17 Mar 2019 20:55:41 +0300 Subject: [PATCH 1/7] 1) `boundsheet` field order fixed 2) `WorkSheet.Visibility` implemented (and typed constants) 3) `WorkSheet.Selected` implemented - most of cases require to parse _current_ sheet, not the first one 4) `WorkSheet.rightToLeft` property for future use (not implemented cause no RtL files present) --- workbook.go | 2 +- worksheet.go | 33 ++++++++++++++++++++++++++------- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/workbook.go b/workbook.go index fd655e5..2ffd0e8 100644 --- a/workbook.go +++ b/workbook.go @@ -246,7 +246,7 @@ func (w *WorkBook) get_string(buf io.ReadSeeker, size uint16) (res string, err e func (w *WorkBook) addSheet(sheet *boundsheet, buf io.ReadSeeker) { name, _ := w.get_string(buf, uint16(sheet.Name)) - w.sheets = append(w.sheets, &WorkSheet{bs: sheet, Name: name, wb: w}) + w.sheets = append(w.sheets, &WorkSheet{bs: sheet, Name: name, wb: w, Visibility: TWorkSheetVisibility(sheet.Visible)}) } //reading a sheet from the compress file to memory, you should call this before you try to get anything from sheet diff --git a/worksheet.go b/worksheet.go index 9bf065c..af549ec 100644 --- a/worksheet.go +++ b/worksheet.go @@ -7,22 +7,33 @@ import ( "unicode/utf16" ) +type TWorkSheetVisibility byte + +const ( + WorkSheetVisible TWorkSheetVisibility = 0 + WorkSheetHidden TWorkSheetVisibility = 1 + WorkSheetVeryHidden TWorkSheetVisibility = 2 +) + type boundsheet struct { Filepos uint32 + Visible TWorkSheetVisibility Type byte - Visible byte Name byte } //WorkSheet in one WorkBook type WorkSheet struct { - bs *boundsheet - wb *WorkBook - Name string - rows map[uint16]*Row + bs *boundsheet + wb *WorkBook + Name string + Selected bool + Visibility TWorkSheetVisibility + rows map[uint16]*Row //NOTICE: this is the max row number of the sheet, so it should be count -1 - MaxRow uint16 - parsed bool + MaxRow uint16 + parsed bool + rightToLeft bool } func (w *WorkSheet) Row(i int) *Row { @@ -56,6 +67,14 @@ func (w *WorkSheet) parseBof(buf io.ReadSeeker, b *bof, pre *bof) *bof { switch b.Id { // case 0x0E5: //MERGEDCELLS // ws.mergedCells(buf) + case 0x23E: // WINDOW2 + var sheetOptions, firstVisibleRow, firstVisibleColumn uint16 + binary.Read(buf, binary.LittleEndian, &sheetOptions) + binary.Read(buf, binary.LittleEndian, &firstVisibleRow) // not valuable + binary.Read(buf, binary.LittleEndian, &firstVisibleColumn) // not valuable + buf.Seek(int64(b.Size)-2*3, 1) + w.rightToLeft = (sheetOptions & 0x40) != 0 + w.Selected = (sheetOptions & 0x400) != 0 case 0x208: //ROW r := new(rowInfo) binary.Read(buf, binary.LittleEndian, r) From 6a276445b275090d9621796a25bf78b3c5dba499 Mon Sep 17 00:00:00 2001 From: Ivan Date: Sun, 17 Mar 2019 22:42:43 +0300 Subject: [PATCH 2/7] 5) Row.ColExact(int)(string) when we need not to output duplicates of merged cells --- row.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/row.go b/row.go index 3100394..0908172 100644 --- a/row.go +++ b/row.go @@ -35,6 +35,17 @@ func (r *Row) Col(i int) string { return "" } +//ColExact Get the Nth Col from the Row, if has not, return nil. +//For merged cells value is returned for first cell only +func (r *Row) ColExact(i int) string { + serial := uint16(i) + if ch, ok := r.cols[serial]; ok { + strs := ch.String(r.wb) + return strs[0] + } + return "" +} + //LastCol Get the number of Last Col of the Row. func (r *Row) LastCol() int { return int(r.info.Lcell) From 0698fa273034a56b3bce78a072f81801b7b2d40a Mon Sep 17 00:00:00 2001 From: Ivan Date: Sun, 17 Mar 2019 22:42:58 +0300 Subject: [PATCH 3/7] 6) Fixed bug on Row.LastCol() when data is actually present but Row.info.Lcell=0 (ms excel shows the data) --- worksheet.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/worksheet.go b/worksheet.go index af549ec..ef51f28 100644 --- a/worksheet.go +++ b/worksheet.go @@ -213,6 +213,9 @@ func (w *WorkSheet) addContent(row_num uint16, ch contentHandler) { info.Index = row_num row = w.addRow(info) } + if row.info.Lcell < ch.LastCol() { + row.info.Lcell = ch.LastCol() + } row.cols[ch.FirstCol()] = ch } From 5480ee505849b7f1cb42ef93353178573cdc3059 Mon Sep 17 00:00:00 2001 From: Ivan Date: Sun, 17 Mar 2019 23:01:02 +0300 Subject: [PATCH 4/7] is obviously preferable for the type that can be ReadBinary --- worksheet.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worksheet.go b/worksheet.go index ef51f28..0299819 100644 --- a/worksheet.go +++ b/worksheet.go @@ -17,7 +17,7 @@ const ( type boundsheet struct { Filepos uint32 - Visible TWorkSheetVisibility + Visible byte Type byte Name byte } From 0bc58acfcb7fc339fafca4a798fe3765db3337f5 Mon Sep 17 00:00:00 2001 From: Ivan Date: Tue, 2 Apr 2019 23:38:51 +0300 Subject: [PATCH 5/7] [fixed] empty value is^W was returned if cell is string formula --- col.go | 18 +++++++++++++++--- workbook.go | 8 ++++---- worksheet.go | 25 +++++++++++++++++++++---- 3 files changed, 40 insertions(+), 11 deletions(-) diff --git a/col.go b/col.go index fa9e02f..a094de4 100644 --- a/col.go +++ b/col.go @@ -54,10 +54,10 @@ func (xf *XfRk) String(wb *WorkBook) string { fNo := wb.Xfs[idx].formatNo() if fNo >= 164 { // user defined format if formatter := wb.Formats[fNo]; formatter != nil { - if (strings.Contains(formatter.str, "#") || strings.Contains(formatter.str, ".00")){ + if strings.Contains(formatter.str, "#") || strings.Contains(formatter.str, ".00") { //If format contains # or .00 then this is a number - return xf.Rk.String() - }else{ + return xf.Rk.String() + } else { i, f, isFloat := xf.Rk.number() if !isFloat { f = float64(i) @@ -165,6 +165,18 @@ func (c *NumberCol) String(wb *WorkBook) []string { return []string{strconv.FormatFloat(c.Float, 'f', -1, 64)} } +type FormulaStringCol struct { + Col + RenderedValue string +} + +func (c *FormulaStringCol) String(wb *WorkBook) []string { + return []string{c.RenderedValue} +} + +//str, err = wb.get_string(buf_item, size) +//wb.sst[offset_pre] = wb.sst[offset_pre] + str + type FormulaCol struct { Header struct { Col diff --git a/workbook.go b/workbook.go index 2ffd0e8..ebaac26 100644 --- a/workbook.go +++ b/workbook.go @@ -3,10 +3,10 @@ package xls import ( "bytes" "encoding/binary" + "golang.org/x/text/encoding/charmap" "io" "os" "unicode/utf16" - "golang.org/x/text/encoding/charmap" ) //xls workbook type @@ -162,9 +162,9 @@ func (wb *WorkBook) parseBof(buf io.ReadSeeker, b *bof, pre *bof, offset_pre int return } func decodeWindows1251(enc []byte) string { - dec := charmap.Windows1251.NewDecoder() - out, _ := dec.Bytes(enc) - return string(out) + dec := charmap.Windows1251.NewDecoder() + out, _ := dec.Bytes(enc) + return string(out) } func (w *WorkBook) get_string(buf io.ReadSeeker, size uint16) (res string, err error) { if w.Is5ver { diff --git a/worksheet.go b/worksheet.go index 0299819..0f4ec8b 100644 --- a/worksheet.go +++ b/worksheet.go @@ -1,6 +1,7 @@ package xls import ( + "bytes" "encoding/binary" "fmt" "io" @@ -48,9 +49,10 @@ func (w *WorkSheet) parse(buf io.ReadSeeker) { w.rows = make(map[uint16]*Row) b := new(bof) var bof_pre *bof + var col_pre interface{} for { if err := binary.Read(buf, binary.LittleEndian, b); err == nil { - bof_pre = w.parseBof(buf, b, bof_pre) + bof_pre, col_pre = w.parseBof(buf, b, bof_pre, col_pre) if b.Id == 0xa { break } @@ -62,8 +64,11 @@ func (w *WorkSheet) parse(buf io.ReadSeeker) { w.parsed = true } -func (w *WorkSheet) parseBof(buf io.ReadSeeker, b *bof, pre *bof) *bof { +func (w *WorkSheet) parseBof(buf io.ReadSeeker, b *bof, pre *bof, col_pre interface{}) (*bof, interface{}) { var col interface{} + var bts = make([]byte, b.Size) + binary.Read(buf, binary.LittleEndian, bts) + buf = bytes.NewReader(bts) switch b.Id { // case 0x0E5: //MERGEDCELLS // ws.mergedCells(buf) @@ -72,7 +77,7 @@ func (w *WorkSheet) parseBof(buf io.ReadSeeker, b *bof, pre *bof) *bof { binary.Read(buf, binary.LittleEndian, &sheetOptions) binary.Read(buf, binary.LittleEndian, &firstVisibleRow) // not valuable binary.Read(buf, binary.LittleEndian, &firstVisibleColumn) // not valuable - buf.Seek(int64(b.Size)-2*3, 1) + //buf.Seek(int64(b.Size)-2*3, 1) w.rightToLeft = (sheetOptions & 0x40) != 0 w.Selected = (sheetOptions & 0x400) != 0 case 0x208: //ROW @@ -108,6 +113,18 @@ func (w *WorkSheet) parseBof(buf io.ReadSeeker, b *bof, pre *bof) *bof { c.Bts = make([]byte, b.Size-20) binary.Read(buf, binary.LittleEndian, &c.Bts) col = c + case 0x207: //STRING = FORMULA-VALUE is expected right after FORMULA + if ch, ok := col_pre.(*FormulaCol); ok { + c := new(FormulaStringCol) + c.Col = ch.Header.Col + var cStringLen uint16 + binary.Read(buf, binary.LittleEndian, &cStringLen) + str, err := w.wb.get_string(buf, cStringLen) + if nil == err { + c.RenderedValue = str + } + col = c + } case 0x27e: //RK col = new(RkCol) binary.Read(buf, binary.LittleEndian, col) @@ -182,7 +199,7 @@ func (w *WorkSheet) parseBof(buf io.ReadSeeker, b *bof, pre *bof) *bof { if col != nil { w.add(col) } - return b + return b, col } func (w *WorkSheet) add(content interface{}) { From 0ecbf4c42c25573bf0f48bd5b9124fe9986bc6d7 Mon Sep 17 00:00:00 2001 From: Federico Simoncelli Date: Fri, 27 Mar 2020 21:08:23 +0100 Subject: [PATCH 6/7] fix format --- col.go | 9 ++++----- workbook.go | 7 ++++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/col.go b/col.go index fa9e02f..9ba155d 100644 --- a/col.go +++ b/col.go @@ -8,7 +8,7 @@ import ( "time" - "github.com/extrame/goyymmdd" + yymmdd "github.com/extrame/goyymmdd" ) //content type @@ -54,16 +54,15 @@ func (xf *XfRk) String(wb *WorkBook) string { fNo := wb.Xfs[idx].formatNo() if fNo >= 164 { // user defined format if formatter := wb.Formats[fNo]; formatter != nil { - if (strings.Contains(formatter.str, "#") || strings.Contains(formatter.str, ".00")){ + if strings.Contains(formatter.str, "#") || strings.Contains(formatter.str, ".00") { //If format contains # or .00 then this is a number - return xf.Rk.String() - }else{ + return xf.Rk.String() + } else { i, f, isFloat := xf.Rk.number() if !isFloat { f = float64(i) } t := timeFromExcelTime(f, wb.dateMode == 1) - return yymmdd.Format(t, formatter.str) } } diff --git a/workbook.go b/workbook.go index fd655e5..4ebb605 100644 --- a/workbook.go +++ b/workbook.go @@ -6,6 +6,7 @@ import ( "io" "os" "unicode/utf16" + "golang.org/x/text/encoding/charmap" ) @@ -162,9 +163,9 @@ func (wb *WorkBook) parseBof(buf io.ReadSeeker, b *bof, pre *bof, offset_pre int return } func decodeWindows1251(enc []byte) string { - dec := charmap.Windows1251.NewDecoder() - out, _ := dec.Bytes(enc) - return string(out) + dec := charmap.Windows1251.NewDecoder() + out, _ := dec.Bytes(enc) + return string(out) } func (w *WorkBook) get_string(buf io.ReadSeeker, size uint16) (res string, err error) { if w.Is5ver { From b8b06ac1dde6a979a4c9200e9ce19ad8e92bac63 Mon Sep 17 00:00:00 2001 From: Federico Simoncelli Date: Fri, 27 Mar 2020 21:08:35 +0100 Subject: [PATCH 7/7] support date format --- col.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/col.go b/col.go index 9ba155d..c1d53d6 100644 --- a/col.go +++ b/col.go @@ -161,6 +161,10 @@ type NumberCol struct { } func (c *NumberCol) String(wb *WorkBook) []string { + if fNo := wb.Xfs[c.Index].formatNo(); fNo != 0 { + t := timeFromExcelTime(c.Float, wb.dateMode == 1) + return []string{yymmdd.Format(t, wb.Formats[fNo].str)} + } return []string{strconv.FormatFloat(c.Float, 'f', -1, 64)} }