Browse Source

Merge pull request #70 from shibukawa/feature/add_cell_types

add cell types like int, float, boolean, formula
Geoffrey J. Teale 11 years ago
parent
commit
72f397156f
8 changed files with 323 additions and 20 deletions
  1. 101 0
      cell.go
  2. 25 0
      cell_test.go
  3. 51 2
      file_test.go
  4. 27 11
      lib.go
  5. 92 0
      lib_test.go
  6. 22 3
      sheet.go
  7. BIN
      testdocs/testcelltypes.xlsx
  8. 5 4
      xmlWorksheet.go

+ 101 - 0
cell.go

@@ -6,15 +6,28 @@ import (
 	"strconv"
 	"strconv"
 )
 )
 
 
+type CellType int
+
+const (
+	CellTypeString CellType = iota
+	CellTypeFormula
+	CellTypeNumeric
+	CellTypeBool
+	CellTypeInline
+	CellTypeError
+)
+
 // Cell is a high level structure intended to provide user access to
 // Cell is a high level structure intended to provide user access to
 // the contents of Cell within an xlsx.Row.
 // the contents of Cell within an xlsx.Row.
 type Cell struct {
 type Cell struct {
 	Row      Row
 	Row      Row
 	Value    string
 	Value    string
+	formula  string
 	style    Style
 	style    Style
 	numFmt   string
 	numFmt   string
 	date1904 bool
 	date1904 bool
 	Hidden   bool
 	Hidden   bool
+	cellType CellType
 }
 }
 
 
 // CellInterface defines the public API of the Cell.
 // CellInterface defines the public API of the Cell.
@@ -27,11 +40,99 @@ func NewCell(r Row) *Cell {
 	return &Cell{style: *NewStyle(), Row: r}
 	return &Cell{style: *NewStyle(), Row: r}
 }
 }
 
 
+func (c *Cell) Type() CellType {
+	return c.cellType
+}
+
+// Set string
+func (c *Cell) SetString(s string) {
+	c.Value = s
+	c.formula = ""
+	c.cellType = CellTypeString
+}
+
 // String returns the value of a Cell as a string.
 // String returns the value of a Cell as a string.
 func (c *Cell) String() string {
 func (c *Cell) String() string {
 	return c.FormattedValue()
 	return c.FormattedValue()
 }
 }
 
 
+// Set float
+func (c *Cell) SetFloat(n float64) {
+	c.SetFloatWithFormat(n, "0.00e+00")
+}
+
+/*
+	Set float with format. The followings are samples of format samples.
+
+	* "0.00e+00"
+	* "0", "#,##0"
+	* "0.00", "#,##0.00", "@"
+	* "#,##0 ;(#,##0)", "#,##0 ;[red](#,##0)"
+	* "#,##0.00;(#,##0.00)", "#,##0.00;[red](#,##0.00)"
+	* "0%", "0.00%"
+	* "0.00e+00", "##0.0e+0"
+*/
+func (c *Cell) SetFloatWithFormat(n float64, format string) {
+	// tmp value. final value is formatted by FormattedValue() method
+	c.Value = fmt.Sprintf("%e", n)
+	c.numFmt = format
+	c.Value = c.FormattedValue()
+	c.formula = ""
+	c.cellType = CellTypeNumeric
+}
+
+// Returns the value of cell as a number
+func (c *Cell) Float() (float64, error) {
+	f, err := strconv.ParseFloat(c.Value, 64)
+	if err != nil {
+		return math.NaN(), err
+	}
+	return f, nil
+}
+
+// Set integer
+func (c *Cell) SetInt(n int) {
+	c.Value = fmt.Sprintf("%d", n)
+	c.numFmt = "0"
+	c.formula = ""
+	c.cellType = CellTypeNumeric
+}
+
+// Returns the value of cell as integer
+func (c *Cell) Int() (int, error) {
+	f, err := strconv.ParseFloat(c.Value, 64)
+	if err != nil {
+		return -1, err
+	}
+	return int(f), nil
+}
+
+// Set boolean
+func (c *Cell) SetBool(b bool) {
+	if b {
+		c.Value = "1"
+	} else {
+		c.Value = "0"
+	}
+	c.cellType = CellTypeBool
+}
+
+// Get boolean
+func (c *Cell) Bool() bool {
+	return c.Value == "1"
+}
+
+// Set formula
+func (c *Cell) SetFormula(formula string) {
+	c.formula = formula
+	c.cellType = CellTypeFormula
+}
+
+// Returns formula
+func (c *Cell) Formula() string {
+	return c.formula
+}
+
 // GetStyle returns the Style associated with a Cell
 // GetStyle returns the Style associated with a Cell
 func (c *Cell) GetStyle() Style {
 func (c *Cell) GetStyle() Style {
 	return c.style
 	return c.style

+ 25 - 0
cell_test.go

@@ -255,3 +255,28 @@ func (l *CellSuite) TestFormattedValue(c *C) {
 	smallCell.numFmt = "yyyy-mm-dd hh:mm:ss"
 	smallCell.numFmt = "yyyy-mm-dd hh:mm:ss"
 	c.Assert(smallCell.FormattedValue(), Equals, "1899-12-30 00:14:47")
 	c.Assert(smallCell.FormattedValue(), Equals, "1899-12-30 00:14:47")
 }
 }
+
+// test setters and getters
+func (s *CellSuite) TestSetterGetters(c *C) {
+	cell := Cell{}
+
+	cell.SetString("hello world")
+	c.Assert(cell.String(), Equals, "hello world")
+	c.Assert(cell.Type(), Equals, CellTypeString)
+
+	cell.SetInt(1024)
+	intValue, _ := cell.Int()
+	c.Assert(intValue, Equals, 1024)
+	c.Assert(cell.Type(), Equals, CellTypeNumeric)
+
+	cell.SetFloat(1.024)
+	float, _ := cell.Float()
+	intValue, _ = cell.Int() // convert
+	c.Assert(float, Equals, 1.024)
+	c.Assert(intValue, Equals, 1)
+	c.Assert(cell.Type(), Equals, CellTypeNumeric)
+
+	cell.SetFormula("10+20")
+	c.Assert(cell.Formula(), Equals, "10+20")
+	c.Assert(cell.Type(), Equals, CellTypeFormula)
+}

+ 51 - 2
file_test.go

@@ -264,11 +264,11 @@ func (l *FileSuite) TestMarshalFile(c *C) {
 	sheet1 := f.AddSheet("MySheet")
 	sheet1 := f.AddSheet("MySheet")
 	row1 := sheet1.AddRow()
 	row1 := sheet1.AddRow()
 	cell1 := row1.AddCell()
 	cell1 := row1.AddCell()
-	cell1.Value = "A cell!"
+	cell1.SetString("A cell!")
 	sheet2 := f.AddSheet("AnotherSheet")
 	sheet2 := f.AddSheet("AnotherSheet")
 	row2 := sheet2.AddRow()
 	row2 := sheet2.AddRow()
 	cell2 := row2.AddCell()
 	cell2 := row2.AddCell()
-	cell2.Value = "A cell!"
+	cell2.SetString("A cell!")
 	parts, err := f.MarshallParts()
 	parts, err := f.MarshallParts()
 	c.Assert(err, IsNil)
 	c.Assert(err, IsNil)
 	c.Assert(len(parts), Equals, 11)
 	c.Assert(len(parts), Equals, 11)
@@ -715,3 +715,52 @@ func fileToSliceCheckOutput(c *C, output [][][]string) {
 	c.Assert(len(output[1]), Equals, 0)
 	c.Assert(len(output[1]), Equals, 0)
 	c.Assert(len(output[2]), Equals, 0)
 	c.Assert(len(output[2]), Equals, 0)
 }
 }
+
+func (l *FileSuite) TestReadWorkbookWithTypes(c *C) {
+	var xlsxFile *File
+	var err error
+
+	xlsxFile, err = OpenFile("./testdocs/testcelltypes.xlsx")
+	c.Assert(err, IsNil)
+	c.Assert(len(xlsxFile.Sheets), Equals, 1)
+	sheet := xlsxFile.Sheet["Sheet1"]
+	c.Assert(len(sheet.Rows), Equals, 8)
+	c.Assert(len(sheet.Rows[0].Cells), Equals, 2)
+
+	// string 1
+	c.Assert(sheet.Rows[0].Cells[0].Type(), Equals, CellTypeString)
+	c.Assert(sheet.Rows[0].Cells[0].String(), Equals, "hello world")
+
+	// string 2
+	c.Assert(sheet.Rows[1].Cells[0].Type(), Equals, CellTypeString)
+	c.Assert(sheet.Rows[1].Cells[0].String(), Equals, "日本語")
+
+	// integer
+	c.Assert(sheet.Rows[2].Cells[0].Type(), Equals, CellTypeNumeric)
+	intValue, _ := sheet.Rows[2].Cells[0].Int()
+	c.Assert(intValue, Equals, 12345)
+
+	// float
+	c.Assert(sheet.Rows[3].Cells[0].Type(), Equals, CellTypeNumeric)
+	floatValue, _ := sheet.Rows[3].Cells[0].Float()
+	c.Assert(floatValue, Equals, 1.024)
+
+	// Now it can't detect date
+	c.Assert(sheet.Rows[4].Cells[0].Type(), Equals, CellTypeNumeric)
+	intValue, _ = sheet.Rows[4].Cells[0].Int()
+	c.Assert(intValue, Equals, 40543)
+
+	// bool
+	c.Assert(sheet.Rows[5].Cells[0].Type(), Equals, CellTypeBool)
+	c.Assert(sheet.Rows[5].Cells[0].Bool(), Equals, true)
+
+	// formula
+	c.Assert(sheet.Rows[6].Cells[0].Type(), Equals, CellTypeFormula)
+	c.Assert(sheet.Rows[6].Cells[0].Formula(), Equals, "10+20")
+	c.Assert(sheet.Rows[6].Cells[0].Value, Equals, "30")
+
+	// error
+	c.Assert(sheet.Rows[7].Cells[0].Type(), Equals, CellTypeError)
+	c.Assert(sheet.Rows[7].Cells[0].Formula(), Equals, "10/0")
+	c.Assert(sheet.Rows[7].Cells[0].Value, Equals, "#DIV/0!")
+}

+ 27 - 11
lib.go

@@ -301,11 +301,10 @@ func makeRowFromRaw(rawrow xlsxRow) *Row {
 	return row
 	return row
 }
 }
 
 
-// getValueFromCellData attempts to extract a valid value, usable in
+// fillCellData attempts to extract a valid value, usable in
 // CSV form from the raw cell value.  Note - this is not actually
 // CSV form from the raw cell value.  Note - this is not actually
 // general enough - we should support retaining tabs and newlines.
 // general enough - we should support retaining tabs and newlines.
-func getValueFromCellData(rawcell xlsxC, reftable *RefTable) string {
-	var value string = ""
+func fillCellData(rawcell xlsxC, reftable *RefTable, cell *Cell) {
 	var data string = rawcell.V
 	var data string = rawcell.V
 	if len(data) > 0 {
 	if len(data) > 0 {
 		vval := strings.Trim(data, " \t\n\r")
 		vval := strings.Trim(data, " \t\n\r")
@@ -315,12 +314,28 @@ func getValueFromCellData(rawcell xlsxC, reftable *RefTable) string {
 			if error != nil {
 			if error != nil {
 				panic(error)
 				panic(error)
 			}
 			}
-			value = reftable.ResolveSharedString(ref)
+			cell.Value = reftable.ResolveSharedString(ref)
+			cell.cellType = CellTypeString
+		case "b": // Boolean
+			cell.Value = vval
+			cell.cellType = CellTypeBool
+		case "e": // Error
+			cell.Value = vval
+			cell.formula = strings.Trim(rawcell.F, " \t\n\r")
+			cell.cellType = CellTypeError
 		default:
 		default:
-			value = vval
+			if len(rawcell.F) == 0 {
+				// Numeric
+				cell.Value = vval
+				cell.cellType = CellTypeNumeric
+			} else {
+				// Formula
+				cell.Value = vval
+				cell.formula = strings.Trim(rawcell.F, " \t\n\r")
+				cell.cellType = CellTypeFormula
+			}
 		}
 		}
 	}
 	}
-	return value
 }
 }
 
 
 // readRowsFromSheet is an internal helper function that extracts the
 // readRowsFromSheet is an internal helper function that extracts the
@@ -404,13 +419,14 @@ func readRowsFromSheet(Worksheet *xlsxWorksheet, file *File) ([]*Row, []*Col, in
 				insertColIndex++
 				insertColIndex++
 			}
 			}
 			cellX := insertColIndex - minCol
 			cellX := insertColIndex - minCol
-			row.Cells[cellX].Value = getValueFromCellData(rawcell, reftable)
+			cell := row.Cells[cellX]
+			fillCellData(rawcell, reftable, cell)
 			if file.styles != nil {
 			if file.styles != nil {
-				row.Cells[cellX].style = file.styles.getStyle(rawcell.S)
-				row.Cells[cellX].numFmt = file.styles.getNumberFormat(rawcell.S)
+				cell.style = file.styles.getStyle(rawcell.S)
+				cell.numFmt = file.styles.getNumberFormat(rawcell.S)
 			}
 			}
-			row.Cells[cellX].date1904 = file.Date1904
-			row.Cells[cellX].Hidden = rawrow.Hidden || (len(cols) > cellX && cols[cellX].Hidden)
+			cell.date1904 = file.Date1904
+			cell.Hidden = rawrow.Hidden || (len(cols) > cellX && cell.Hidden)
 			insertColIndex++
 			insertColIndex++
 		}
 		}
 		if len(rows) > insertRowIndex-minRow {
 		if len(rows) > insertRowIndex-minRow {

+ 92 - 0
lib_test.go

@@ -609,3 +609,95 @@ func (l *LibSuite) TestReadRowsFromSheetWithMultipleSpans(c *C) {
 	c.Assert(cell4.Value, Equals, "Bar")
 	c.Assert(cell4.Value, Equals, "Bar")
 
 
 }
 }
+
+func (l *LibSuite) TestReadRowsFromSheetWithMultipleTypes(c *C) {
+	var sharedstringsXML = bytes.NewBufferString(`
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="4" uniqueCount="4">
+  <si>
+    <t>Hello World</t>
+  </si>
+</sst>`)
+	var sheetxml = bytes.NewBufferString(`
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"
+           xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
+  <dimension ref="A1:F1"/>
+  <sheetViews>
+    <sheetView tabSelected="1" workbookViewId="0">
+      <selection activeCell="C1" sqref="C1"/>
+    </sheetView>
+  </sheetViews>
+  <sheetFormatPr baseColWidth="10" defaultRowHeight="15"/>
+  <sheetData>
+    <row r="1" spans="1:6">
+      <c r="A1" t="s">
+        <v>0</v>
+      </c>
+      <c r="B1">
+        <v>12345</v>
+      </c>
+      <c r="C1">
+        <v>1.024</v>
+      </c>
+      <c r="D1" t="b">
+        <v>1</v>
+      </c>
+      <c r="E1">
+      	<f>10+20</f>
+        <v>30</v>
+      </c>
+      <c r="F1" t="e">
+      	<f>10/0</f>
+        <v>#DIV/0!</v>
+      </c>
+    </row>
+  </sheetData>
+  <pageMargins left="0.7" right="0.7"
+               top="0.78740157499999996"
+               bottom="0.78740157499999996"
+               header="0.3"
+               footer="0.3"/>
+</worksheet>`)
+	worksheet := new(xlsxWorksheet)
+	err := xml.NewDecoder(sheetxml).Decode(worksheet)
+	c.Assert(err, IsNil)
+	sst := new(xlsxSST)
+	err = xml.NewDecoder(sharedstringsXML).Decode(sst)
+	c.Assert(err, IsNil)
+	file := new(File)
+	file.referenceTable = MakeSharedStringRefTable(sst)
+	rows, _, maxCols, maxRows := readRowsFromSheet(worksheet, file)
+	c.Assert(maxRows, Equals, 1)
+	c.Assert(maxCols, Equals, 6)
+	row := rows[0]
+	c.Assert(len(row.Cells), Equals, 6)
+
+	cell1 := row.Cells[0]
+	c.Assert(cell1.Type(), Equals, CellTypeString)
+	c.Assert(cell1.String(), Equals, "Hello World")
+
+	cell2 := row.Cells[1]
+	c.Assert(cell2.Type(), Equals, CellTypeNumeric)
+	intValue, _ := cell2.Int()
+	c.Assert(intValue, Equals, 12345)
+
+	cell3 := row.Cells[2]
+	c.Assert(cell3.Type(), Equals, CellTypeNumeric)
+	float, _ := cell3.Float()
+	c.Assert(float, Equals, 1.024)
+
+	cell4 := row.Cells[3]
+	c.Assert(cell4.Type(), Equals, CellTypeBool)
+	c.Assert(cell4.Bool(), Equals, true)
+
+	cell5 := row.Cells[4]
+	c.Assert(cell5.Type(), Equals, CellTypeFormula)
+	c.Assert(cell5.Formula(), Equals, "10+20")
+	c.Assert(cell5.Value, Equals, "30")
+
+	cell6 := row.Cells[5]
+	c.Assert(cell6.Type(), Equals, CellTypeError)
+	c.Assert(cell6.Formula(), Equals, "10/0")
+	c.Assert(cell6.Value, Equals, "#DIV/0!")
+}

+ 22 - 3
sheet.go

@@ -93,9 +93,28 @@ func (s *Sheet) makeXLSXSheet(refTable *RefTable, styles *xlsxStyleSheet) *xlsxW
 			}
 			}
 			xC := xlsxC{}
 			xC := xlsxC{}
 			xC.R = fmt.Sprintf("%s%d", numericToLetters(c), r+1)
 			xC.R = fmt.Sprintf("%s%d", numericToLetters(c), r+1)
-			xC.V = strconv.Itoa(refTable.AddString(cell.Value))
-			xC.T = "s" // Hardcode string type, for now.
-			xC.S = XfId
+			switch cell.cellType {
+			case CellTypeString:
+				xC.V = strconv.Itoa(refTable.AddString(cell.Value))
+				xC.T = "s"
+				xC.S = XfId
+			case CellTypeBool:
+				xC.V = cell.Value
+				xC.T = "b"
+				xC.S = XfId
+			case CellTypeNumeric:
+				xC.V = cell.Value
+				xC.S = XfId
+			case CellTypeFormula:
+				xC.V = cell.Value
+				xC.F = cell.formula
+				xC.S = XfId
+			case CellTypeError:
+				xC.V = cell.Value
+				xC.F = cell.formula
+				xC.T = "e"
+				xC.S = XfId
+			}
 			xRow.C = append(xRow.C, xC)
 			xRow.C = append(xRow.C, xC)
 		}
 		}
 		xSheet.Row = append(xSheet.Row, xRow)
 		xSheet.Row = append(xSheet.Row, xRow)

BIN
testdocs/testcelltypes.xlsx


+ 5 - 4
xmlWorksheet.go

@@ -218,10 +218,11 @@ type xlsxRow struct {
 // currently I have not checked it for completeness - it does as much
 // currently I have not checked it for completeness - it does as much
 // as I need.
 // as I need.
 type xlsxC struct {
 type xlsxC struct {
-	R string `xml:"r,attr"` // Cell ID, e.g. A1
-	S int    `xml:"s,attr"` // Style reference.
-	T string `xml:"t,attr"` // Type.
-	V string `xml:"v"`      // Value
+	R string `xml:"r,attr"`           // Cell ID, e.g. A1
+	S int    `xml:"s,attr"`           // Style reference.
+	T string `xml:"t,attr,omitempty"` // Type.
+	V string `xml:"v"`                // Value
+	F string `xml:"f,omitempty"`      // Formula
 }
 }
 
 
 // Create a new XLSX Worksheet with default values populated.
 // Create a new XLSX Worksheet with default values populated.