diff --git a/cell_test.go b/cell_test.go
index fb1e8ef585..48a1c2fb77 100644
--- a/cell_test.go
+++ b/cell_test.go
@@ -224,7 +224,7 @@ func TestGetCellValue(t *testing.T) {
f.checked = nil
cells := []string{"A3", "A4", "B4", "A7", "B7"}
rows, err := f.GetRows("Sheet1")
- assert.Equal(t, [][]string{nil, nil, {"A3"}, {"A4", "B4"}, nil, nil, {"A7", "B7"}, {"A8", "B8"}}, rows)
+ assert.Equal(t, [][]Cell{nil, nil, {Cell{Value: "A3"}}, {Cell{Value: "A4"}, Cell{Value: "B4"}}, nil, nil, {Cell{Value: "A7"}, Cell{Value: "B7"}}, {Cell{Value: "A8"}, Cell{Value: "B8"}}}, rows)
assert.NoError(t, err)
for _, cell := range cells {
value, err := f.GetCellValue("Sheet1", cell)
@@ -246,21 +246,21 @@ func TestGetCellValue(t *testing.T) {
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `A2
B2
`)))
f.checked = nil
rows, err = f.GetRows("Sheet1")
- assert.Equal(t, [][]string{nil, {"A2", "B2"}}, rows)
+ assert.Equal(t, [][]Cell{nil, {Cell{Value: "A2"}, Cell{Value: "B2"}}}, rows)
assert.NoError(t, err)
f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `A1
B1
`)))
f.checked = nil
rows, err = f.GetRows("Sheet1")
- assert.Equal(t, [][]string{{"A1", "B1"}}, rows)
+ assert.Equal(t, [][]Cell{{Cell{Value: "A1"}, Cell{Value: "B1"}}}, rows)
assert.NoError(t, err)
f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `A3
A4B4
A7B7
A8B8
`)))
f.checked = nil
rows, err = f.GetRows("Sheet1")
- assert.Equal(t, [][]string{{"A3"}, {"A4", "B4"}, nil, nil, nil, nil, {"A7", "B7"}, {"A8", "B8"}}, rows)
+ assert.Equal(t, [][]Cell{{Cell{Value: "A3"}}, {Cell{Value: "A4"}, Cell{Value: "B4"}}, nil, nil, nil, nil, {Cell{Value: "A7"}, Cell{Value: "B7"}}, {Cell{Value: "A8"}, Cell{Value: "B8"}}}, rows)
assert.NoError(t, err)
f.Sheet.Delete("xl/worksheets/sheet1.xml")
@@ -270,13 +270,13 @@ func TestGetCellValue(t *testing.T) {
assert.Equal(t, "H6", cell)
assert.NoError(t, err)
rows, err = f.GetRows("Sheet1")
- assert.Equal(t, [][]string{
- {"A6", "B6", "C6"},
+ assert.Equal(t, [][]Cell{
+ {Cell{Value: "A6"}, Cell{Value: "B6"}, Cell{Value: "C6"}},
nil,
- {"100", "B3"},
- {"", "", "", "", "", "F4"},
+ {Cell{Value: int64(100)}, Cell{Value: "B3"}},
+ {Cell{}, Cell{}, Cell{}, Cell{}, Cell{}, Cell{Value: "F4"}},
nil,
- {"", "", "", "", "", "", "", "H6"},
+ {Cell{}, Cell{}, Cell{}, Cell{}, Cell{}, Cell{}, Cell{}, Cell{Value: "H6"}},
}, rows)
assert.NoError(t, err)
@@ -314,36 +314,36 @@ func TestGetCellValue(t *testing.T) {
`)))
f.checked = nil
rows, err = f.GetRows("Sheet1")
- assert.Equal(t, [][]string{{
- "2422.3",
- "2422.3",
- "12.4",
- "964",
- "1101.6",
- "275.4",
- "68.9",
- "44385.2083333333",
- "5.1",
- "5.11",
- "5.1",
- "5.111",
- "5.1111",
- "2422.012345678",
- "2422.0123456789",
- "12.012345678901",
- "964",
- "1101.6",
- "275.4",
- "68.9",
- "0.08888",
- "0.00004",
- "2422.3",
- "1101.6",
- "275.4",
- "68.9",
- "1.1",
- "1234567890123_4",
- "123456789_0123_4",
+ assert.Equal(t, [][]Cell{{
+ Cell{Value: 2422.3},
+ Cell{Value: 2422.3},
+ Cell{Value: 12.4},
+ Cell{Value: int64(964)},
+ Cell{Value: 1101.6},
+ Cell{Value: 275.4},
+ Cell{Value: 68.9},
+ Cell{Value: 44385.2083333333},
+ Cell{Value: 5.1},
+ Cell{Value: 5.11},
+ Cell{Value: 5.1},
+ Cell{Value: 5.111},
+ Cell{Value: 5.1111},
+ Cell{Value: 2422.012345678},
+ Cell{Value: 2422.0123456789},
+ Cell{Value: 12.012345678901},
+ Cell{Value: int64(964)},
+ Cell{Value: 1101.6},
+ Cell{Value: 275.4},
+ Cell{Value: 68.9},
+ Cell{Value: 0.08888},
+ Cell{Value: 0.00004},
+ Cell{Value: 2422.3},
+ Cell{Value: 1101.6},
+ Cell{Value: 275.4},
+ Cell{Value: 68.9},
+ Cell{Value: 1.1},
+ Cell{Value: "1234567890123_4"},
+ Cell{Value: "123456789_0123_4"},
}}, rows)
assert.NoError(t, err)
}
diff --git a/excelize_test.go b/excelize_test.go
index f1b9903cbb..20368d0275 100644
--- a/excelize_test.go
+++ b/excelize_test.go
@@ -1114,12 +1114,12 @@ func TestSharedStrings(t *testing.T) {
if !assert.NoError(t, err) {
t.FailNow()
}
- assert.Equal(t, "A", rows[0][0])
+ assert.Equal(t, Cell{Value: "A"}, rows[0][0])
rows, err = f.GetRows("Sheet2")
if !assert.NoError(t, err) {
t.FailNow()
}
- assert.Equal(t, "Test Weight (Kgs)", rows[0][0])
+ assert.Equal(t, Cell{Value: "Test Weight (Kgs)"}, rows[0][0])
assert.NoError(t, f.Close())
}
diff --git a/lib.go b/lib.go
index 99118ff079..75d24a30c4 100644
--- a/lib.go
+++ b/lib.go
@@ -698,6 +698,9 @@ func (f *File) addSheetNameSpace(sheet string, ns xml.Attr) {
// the precision for the numeric.
func isNumeric(s string) (bool, int) {
dot, e, n, p := false, false, false, 0
+ if s == "" {
+ return false, 0
+ }
for i, v := range s {
if v == '.' {
if dot {
diff --git a/rows.go b/rows.go
index 853c8f7df3..e661670615 100644
--- a/rows.go
+++ b/rows.go
@@ -49,12 +49,12 @@ import (
// fmt.Println()
// }
//
-func (f *File) GetRows(sheet string, opts ...Options) ([][]string, error) {
+func (f *File) GetRows(sheet string, opts ...Options) ([][]Cell, error) {
rows, err := f.Rows(sheet)
if err != nil {
return nil, err
}
- results, cur, max := make([][]string, 0, 64), 0, 0
+ results, cur, max := make([][]Cell, 0, 64), 0, 0
for rows.Next() {
cur++
row, err := rows.Columns(opts...)
@@ -74,12 +74,14 @@ type Rows struct {
err error
curRow, seekRow int
needClose, rawCellValue bool
- sheet string
+ sheetPath string
+ sheetName string
f *File
tempFile *os.File
sst *xlsxSST
decoder *xml.Decoder
token xml.Token
+ rowOpts RowOpts
}
// Next will return true if find the next row element.
@@ -101,6 +103,17 @@ func (rows *Rows) Next() bool {
rows.curRow = rowNum
}
rows.token = token
+ ro := RowOpts{}
+ if styleID, err := attrValToInt("s", xmlElement.Attr); err == nil && styleID > 0 && styleID < MaxCellStyles {
+ ro.StyleID = styleID
+ }
+ if hidden, err := attrValToBool("hidden", xmlElement.Attr); err == nil {
+ ro.Hidden = hidden
+ }
+ if height, err := attrValToFloat("ht", xmlElement.Attr); err == nil {
+ ro.Height = height
+ }
+ rows.rowOpts = ro
return true
}
case xml.EndElement:
@@ -111,6 +124,13 @@ func (rows *Rows) Next() bool {
}
}
+// GetStyleID will return the RowOpts of the current row.
+//
+// rowOpts := rows.GetRowOpts()
+func (rows *Rows) GetRowOpts() RowOpts {
+ return rows.rowOpts
+}
+
// Error will return the error when the error occurs.
func (rows *Rows) Error() error {
return rows.err
@@ -128,7 +148,7 @@ func (rows *Rows) Close() error {
// Columns return the current row's column values. This fetches the worksheet
// data as a stream, returns each cell in a row as is, and will not skip empty
// rows in the tail of the worksheet.
-func (rows *Rows) Columns(opts ...Options) ([]string, error) {
+func (rows *Rows) Columns(opts ...Options) ([]Cell, error) {
if rows.curRow > rows.seekRow {
return nil, nil
}
@@ -171,9 +191,9 @@ func (rows *Rows) Columns(opts ...Options) ([]string, error) {
}
// appendSpace append blank characters to slice by given length and source slice.
-func appendSpace(l int, s []string) []string {
+func appendSpace(l int, s []Cell) []Cell {
for i := 1; i < l; i++ {
- s = append(s, "")
+ s = append(s, Cell{})
}
return s
}
@@ -192,7 +212,7 @@ type rowXMLIterator struct {
err error
inElement string
cellCol int
- columns []string
+ columns []Cell
}
// rowXMLHandler parse the row XML element of the worksheet.
@@ -206,10 +226,18 @@ func (rows *Rows) rowXMLHandler(rowIterator *rowXMLIterator, xmlElement *xml.Sta
return
}
}
+ //blank := rowIterator.cellCol - len(rowIterator.columns)
+ //if val, _ := colCell.getValueFrom(rows.f, rows.sst, raw); val != "" || colCell.F != nil {
+ // rowIterator.columns = append(appendSpace(blank, rowIterator.columns), val)
+ //}
blank := rowIterator.cellCol - len(rowIterator.columns)
- if val, _ := colCell.getValueFrom(rows.f, rows.sst, raw); val != "" || colCell.F != nil {
- rowIterator.columns = append(appendSpace(blank, rowIterator.columns), val)
+ var formula string
+ if colCell.F != nil {
+ formula, _ = rows.f.GetCellFormula(rows.sheetName, colCell.R)
}
+ //if val, _ := colCell.getTypedValueFrom(rows.f, rows.sst); val != "" || colCell.F != nil {
+ val, _ := colCell.getTypedValueFrom(rows.f, rows.sst)
+ rowIterator.columns = append(appendSpace(blank, rowIterator.columns), Cell{Value: val, StyleID: colCell.S, Formula: formula})
}
}
@@ -249,7 +277,7 @@ func (f *File) Rows(sheet string) (*Rows, error) {
f.saveFileList(name, f.replaceNameSpaceBytes(name, output))
}
var err error
- rows := Rows{f: f, sheet: name}
+ rows := Rows{f: f, sheetPath: name, sheetName: sheet}
rows.needClose, rows.decoder, rows.tempFile, err = f.xmlDecoder(name)
return &rows, err
}
@@ -475,6 +503,61 @@ func (c *xlsxC) getValueFrom(f *File, d *xlsxSST, raw bool) (string, error) {
}
}
+func (c *xlsxC) getTypedValueFrom(f *File, d *xlsxSST) (interface{}, error) {
+ f.Lock()
+ defer f.Unlock()
+ switch c.T {
+ case "b":
+ if c.V == "1" {
+ return true, nil
+ } else if c.V == "0" {
+ return false, nil
+ }
+ case "s":
+ if c.V != "" {
+ xlsxSI := 0
+ xlsxSI, _ = strconv.Atoi(c.V)
+ if _, ok := f.tempFiles.Load(defaultXMLPathSharedStrings); ok {
+ return f.getFromStringItem(xlsxSI), nil
+ }
+ if len(d.SI) > xlsxSI {
+ return d.SI[xlsxSI].String(), nil
+ }
+ }
+ case "str":
+ return c.V, nil
+ case "inlineStr":
+ if c.IS != nil {
+ return c.IS.String(), nil
+ }
+ return c.V, nil
+ default:
+ if isNum, precision := isNumeric(c.V); isNum {
+ var precisionV string
+ if precision == 0 {
+ precisionV = roundPrecision(c.V, 15)
+ } else {
+ precisionV = roundPrecision(c.V, -1)
+ }
+
+ vi, erri := strconv.ParseInt(precisionV, 10, 64)
+ vf, errf := strconv.ParseFloat(precisionV, 64)
+ if erri == nil {
+ return vi, nil
+ } else if errf == nil {
+ return vf, nil
+ } else {
+ return precisionV, nil
+ }
+ } else {
+ return nil, nil
+ }
+ // TODO: add support for other possible values of T (https://stackoverflow.com/questions/18334314/what-do-excel-xml-cell-attribute-values-mean)
+ }
+
+ return c.V, nil
+}
+
// roundPrecision provides a function to format floating-point number text
// with precision, if the given text couldn't be parsed to float, this will
// return the original string.
diff --git a/rows_test.go b/rows_test.go
index 4fe28517cd..fbff60441e 100644
--- a/rows_test.go
+++ b/rows_test.go
@@ -24,11 +24,11 @@ func TestRows(t *testing.T) {
t.FailNow()
}
- var collectedRows [][]string
+ var collectedRows [][]Cell
for rows.Next() {
columns, err := rows.Columns()
assert.NoError(t, err)
- collectedRows = append(collectedRows, trimSliceSpace(columns))
+ collectedRows = append(collectedRows, columns)
}
if !assert.NoError(t, rows.Error()) {
t.FailNow()
@@ -37,9 +37,6 @@ func TestRows(t *testing.T) {
returnedRows, err := f.GetRows(sheet2)
assert.NoError(t, err)
- for i := range returnedRows {
- returnedRows[i] = trimSliceSpace(returnedRows[i])
- }
if !assert.Equal(t, collectedRows, returnedRows) {
t.FailNow()
}
@@ -72,12 +69,30 @@ func TestRowsIterator(t *testing.T) {
rows, err := f.Rows(sheetName)
require.NoError(t, err)
+ expectedCells := [][]Cell{
+ {Cell{Value: "Monitor", StyleID: 1}, Cell{StyleID: 1}, Cell{Value: "Brand", StyleID: 2}, Cell{StyleID: 2}, Cell{Value: "inlineStr"}},
+ {Cell{Value: "> 23 Inch", StyleID: 1}, Cell{Value: int64(19), StyleID: 1}, Cell{Value: "HP", StyleID: 3}, Cell{Value: int64(200), StyleID: 4}},
+ {Cell{Value: "20-23 Inch", StyleID: 1}, Cell{Value: int64(24), StyleID: 1}, Cell{Value: "DELL", StyleID: 3}, Cell{Value: int64(450), StyleID: 4}},
+ {Cell{Value: "17-20 Inch", StyleID: 1}, Cell{Value: int64(56), StyleID: 1}, Cell{Value: "Lenove", StyleID: 3}, Cell{Value: int64(200), StyleID: 4}},
+ {Cell{Value: "< 17 Inch", StyleID: 5}, Cell{Value: int64(21), StyleID: 1}, Cell{Value: "SONY", StyleID: 3}, Cell{Value: int64(510), StyleID: 4}},
+ {Cell{}, Cell{}, Cell{Value: "Acer", StyleID: 3}, Cell{Value: int64(315), StyleID: 4}},
+ {Cell{}, Cell{}, Cell{Value: "IBM", StyleID: 3}, Cell{Value: int64(127), StyleID: 4}},
+ {Cell{}, Cell{}, Cell{Value: "ASUS", StyleID: 4}, Cell{Value: int64(89), StyleID: 4}},
+ {Cell{}, Cell{}, Cell{Value: "Apple", StyleID: 4}, Cell{Value: int64(348), StyleID: 4}},
+ {Cell{}, Cell{}, Cell{Value: "SAMSUNG", StyleID: 4}, Cell{Value: int64(53), StyleID: 4}},
+ {Cell{}, Cell{}, Cell{Value: "Other", StyleID: 4}, Cell{Value: int64(37), StyleID: 4}, Cell{Formula: "B2+B3", StyleID: 4}, Cell{Formula: "IF(B2>0, (D2/B2)*100, 0)", StyleID: 4}, Cell{Formula: "IF(B2>0, (D2/B2)*100, 0)", StyleID: 4}, Cell{Formula: "IF(D2>0, (F2/D2)*100, 0)", StyleID: 4}, Cell{Formula: "IF(D2>0, (F2/D2)*100, 0)", StyleID: 4}},
+ }
+ gotCells := [][]Cell{}
for rows.Next() {
rowCount++
require.True(t, rowCount <= expectedNumRow, "rowCount is greater than expected")
+ cols, err := rows.Columns()
+ require.NoError(t, err)
+ gotCells = append(gotCells, cols)
}
assert.Equal(t, expectedNumRow, rowCount)
+ assert.Equal(t, expectedCells, gotCells)
assert.NoError(t, rows.Close())
assert.NoError(t, f.Close())
@@ -96,6 +111,28 @@ func TestRowsIterator(t *testing.T) {
assert.Equal(t, expectedNumRow, rowCount)
}
+func TestRowsGetRowOpts(t *testing.T) {
+ sheetName := "Sheet2"
+ expectedRowStyleID1 := RowOpts{Height: 17.0, Hidden: false, StyleID: 1}
+ expectedRowStyleID2 := RowOpts{Height: 17.0, Hidden: false, StyleID: 0}
+ expectedRowStyleID3 := RowOpts{Height: 17.0, Hidden: false, StyleID: 2}
+ f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
+ require.NoError(t, err)
+
+ rows, err := f.Rows(sheetName)
+ require.NoError(t, err)
+
+ rows.Next()
+ got := rows.GetRowOpts()
+ assert.Equal(t, expectedRowStyleID1, got)
+ rows.Next()
+ got = rows.GetRowOpts()
+ assert.Equal(t, expectedRowStyleID2, got)
+ rows.Next()
+ got = rows.GetRowOpts()
+ assert.Equal(t, expectedRowStyleID3, got)
+}
+
func TestRowsError(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
if !assert.NoError(t, err) {
diff --git a/sheet.go b/sheet.go
index 01dd1672b9..e984dbd065 100644
--- a/sheet.go
+++ b/sheet.go
@@ -928,6 +928,34 @@ func attrValToInt(name string, attrs []xml.Attr) (val int, err error) {
return
}
+// attrValToFloat provides a function to convert the local names to a float64
+// by given XML attributes and specified names.
+func attrValToFloat(name string, attrs []xml.Attr) (val float64, err error) {
+ for _, attr := range attrs {
+ if attr.Name.Local == name {
+ val, err = strconv.ParseFloat(attr.Value, 64)
+ if err != nil {
+ return
+ }
+ }
+ }
+ return
+}
+
+// attrValToBool provides a function to convert the local names to a boot
+// by given XML attributes and specified names.
+func attrValToBool(name string, attrs []xml.Attr) (val bool, err error) {
+ for _, attr := range attrs {
+ if attr.Name.Local == name {
+ val, err = strconv.ParseBool(attr.Value)
+ if err != nil {
+ return
+ }
+ }
+ }
+ return
+}
+
// SetHeaderFooter provides a function to set headers and footers by given
// worksheet name and the control characters.
//
diff --git a/stream.go b/stream.go
index 1a1af24412..52e65a46c8 100644
--- a/stream.go
+++ b/stream.go
@@ -327,6 +327,9 @@ func (sw *StreamWriter) SetRow(axis string, values []interface{}, opts ...RowOpt
}
fmt.Fprintf(&sw.rawData, ``, row, attrs)
for i, val := range values {
+ if val == nil {
+ continue
+ }
axis, err := CoordinatesToCellName(col+i, row)
if err != nil {
return err
diff --git a/stream_test.go b/stream_test.go
index 6843e2064f..8f6a5b4cf5 100644
--- a/stream_test.go
+++ b/stream_test.go
@@ -209,6 +209,17 @@ func TestSetRow(t *testing.T) {
assert.EqualError(t, streamWriter.SetRow("A", []interface{}{}), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
}
+func TestSetRowNilValues(t *testing.T) {
+ file := NewFile()
+ streamWriter, err := file.NewStreamWriter("Sheet1")
+ assert.NoError(t, err)
+ streamWriter.SetRow("A1", []interface{}{nil, nil, Cell{Value: "foo"}})
+ streamWriter.Flush()
+ ws, err := file.workSheetReader("Sheet1")
+ assert.NoError(t, err)
+ assert.NotEqual(t, ws.SheetData.Row[0].C[0].XMLName.Local, "c")
+}
+
func TestSetCellValFunc(t *testing.T) {
f := NewFile()
sw, err := f.NewStreamWriter("Sheet1")
diff --git a/test/Book1.xlsx b/test/Book1.xlsx
index 6a497e33af..ed3e292954 100644
Binary files a/test/Book1.xlsx and b/test/Book1.xlsx differ
diff --git a/xmlDrawing.go b/xmlDrawing.go
index 3e54b7207f..04956d5745 100644
--- a/xmlDrawing.go
+++ b/xmlDrawing.go
@@ -107,6 +107,7 @@ const (
MaxFieldLength = 255
MaxColumnWidth = 255
MaxRowHeight = 409
+ MaxCellStyles = 64000
MinFontSize = 1
TotalRows = 1048576
MinColumns = 1