Explorar o código

Merge pull request #98 from sirwart/sharedFormulas

Support for shared formulas
Geoffrey J. Teale %!s(int64=11) %!d(string=hai) anos
pai
achega
0a1e73373b
Modificáronse 4 ficheiros con 142 adicións e 8 borrados
  1. 68 5
      lib.go
  2. 60 0
      lib_test.go
  3. 2 2
      sheet.go
  4. 12 1
      xmlWorksheet.go

+ 68 - 5
lib.go

@@ -311,10 +311,72 @@ func makeEmptyRow() *Row {
 	return row
 }
 
+type sharedFormula struct {
+   x, y    int
+   formula string
+}
+
+func formulaForCell(rawcell xlsxC, sharedFormulas map[int]sharedFormula) string {
+   var res string
+
+   f := rawcell.F
+   if f.T == "shared" {
+	   x, y, err := getCoordsFromCellIDString(rawcell.R)
+	   if err != nil {
+		   res = f.Content
+	   } else {
+		   if f.Ref != "" {
+			   res = f.Content
+			   sharedFormulas[f.Si] = sharedFormula{x, y, res}
+		   } else {
+			   sharedFormula := sharedFormulas[f.Si]
+			   dx := x - sharedFormula.x
+			   dy := y - sharedFormula.y
+			   orig := []byte(sharedFormula.formula)
+			   var start, end int
+			   for end = 0; end < len(orig); end++ {
+				   c := orig[end]
+				   if c >= 'A' && c <= 'Z' {
+					   res += string(orig[start:end])
+					   start = end
+					   end++
+					   foundNum := false
+					   for ; end < len(orig); end++ {
+						   idc := orig[end]
+						   if idc >= '0' && idc <= '9' {
+							   foundNum = true
+						   } else if idc >= 'A' && idc <= 'Z' {
+							   if foundNum {
+								   break
+							   }
+						   } else {
+							   break
+						   }
+					   }
+					   if foundNum {
+						   fx, fy, _ := getCoordsFromCellIDString(string(orig[start:end]))
+						   fx += dx
+						   fy += dy
+						   res += getCellIDStringFromCoords(fx, fy)
+						   start = end
+					   }
+				   }
+			   }
+			   if start < len(orig) {
+				   res += string(orig[start:end])
+			   }
+		   }
+	   }
+   } else {
+	   res = f.Content
+   }
+   return strings.Trim(res, " \t\n\r")
+}
+
 // fillCellData attempts to extract a valid value, usable in
 // CSV form from the raw cell value.  Note - this is not actually
 // general enough - we should support retaining tabs and newlines.
-func fillCellData(rawcell xlsxC, reftable *RefTable, cell *Cell) {
+func fillCellData(rawcell xlsxC, reftable *RefTable, sharedFormulas map[int]sharedFormula, cell *Cell) {
 	var data string = rawcell.V
 	if len(data) > 0 {
 		vval := strings.Trim(data, " \t\n\r")
@@ -331,17 +393,17 @@ func fillCellData(rawcell xlsxC, reftable *RefTable, cell *Cell) {
 			cell.cellType = CellTypeBool
 		case "e": // Error
 			cell.Value = vval
-			cell.formula = strings.Trim(rawcell.F, " \t\n\r")
+			cell.formula = formulaForCell(rawcell, sharedFormulas)
 			cell.cellType = CellTypeError
 		default:
-			if len(rawcell.F) == 0 {
+			if rawcell.F == nil {
 				// Numeric
 				cell.Value = vval
 				cell.cellType = CellTypeNumeric
 			} else {
 				// Formula
 				cell.Value = vval
-				cell.formula = strings.Trim(rawcell.F, " \t\n\r")
+				cell.formula = formulaForCell(rawcell, sharedFormulas)
 				cell.cellType = CellTypeFormula
 			}
 		}
@@ -360,6 +422,7 @@ func readRowsFromSheet(Worksheet *xlsxWorksheet, file *File) ([]*Row, []*Col, in
 	var reftable *RefTable
 	var err error
 	var insertRowIndex, insertColIndex int
+	sharedFormulas := map[int]sharedFormula{}
 
 	if len(Worksheet.SheetData.Row) == 0 {
 		return nil, nil, 0, 0
@@ -436,7 +499,7 @@ func readRowsFromSheet(Worksheet *xlsxWorksheet, file *File) ([]*Row, []*Col, in
 			}
 			cellX := insertColIndex
 			cell := row.Cells[cellX]
-			fillCellData(rawcell, reftable, cell)
+			fillCellData(rawcell, reftable, sharedFormulas, cell)
 			if file.styles != nil {
 				cell.style = file.styles.getStyle(rawcell.S)
 				cell.numFmt = file.styles.getNumberFormat(rawcell.S)

+ 60 - 0
lib_test.go

@@ -834,3 +834,63 @@ func (l *LibSuite) TestReadRowFromRawWithPartialCoordinates(c *C) {
 	c.Assert(row, NotNil)
 	c.Assert(row.Cells, HasLen, 27)
 }
+
+func (l *LibSuite) TestSharedFormulas(c *C) {
+	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:C2"/>
+  <sheetViews>
+    <sheetView tabSelected="1" workbookViewId="0">
+      <selection activeCell="C1" sqref="C1"/>
+    </sheetView>
+  </sheetViews>
+  <sheetFormatPr baseColWidth="10" defaultRowHeight="15"/>
+  <sheetData>
+    <row r="1" spans="1:3">
+      <c r="A1">
+        <v>1</v>
+      </c>
+      <c r="B1">
+        <v>2</v>
+      </c>
+      <c r="C1">
+        <v>3</v>
+      </c>
+    </row>
+    <row r="2" spans="1:3">
+      <c r="A2">
+        <v>2</v>
+		<f t="shared" ref="A2:C2" si="0">2*A1</f>
+      </c>
+      <c r="B2">
+        <v>4</v>
+		<f t="shared" si="0"/>
+      </c>
+      <c r="C2">
+        <v>6</v>
+		<f t="shared" si="0"/>
+      </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)
+
+	file := new(File)
+	rows, _, maxCols, maxRows := readRowsFromSheet(worksheet, file)
+	c.Assert(maxCols, Equals, 3)
+	c.Assert(maxRows, Equals, 2)
+
+	row := rows[1]
+	c.Assert(row.Cells[1].Formula(), Equals, "2*B1")
+	c.Assert(row.Cells[2].Formula(), Equals, "2*C1")
+}

+ 2 - 2
sheet.go

@@ -141,11 +141,11 @@ func (s *Sheet) makeXLSXSheet(refTable *RefTable, styles *xlsxStyleSheet) *xlsxW
 				xC.S = XfId
 			case CellTypeFormula:
 				xC.V = cell.Value
-				xC.F = cell.formula
+				xC.F = &xlsxF{Content: cell.formula}
 				xC.S = XfId
 			case CellTypeError:
 				xC.V = cell.Value
-				xC.F = cell.formula
+				xC.F = &xlsxF{Content: cell.formula}
 				xC.T = "e"
 				xC.S = XfId
 			}

+ 12 - 1
xmlWorksheet.go

@@ -235,7 +235,18 @@ type xlsxC struct {
 	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
+	F *xlsxF `xml:"f,omitempty"`      // Formula
+}
+
+// xlsxC directly maps the f element in the namespace
+// http://schemas.openxmlformats.org/sprceadsheetml/2006/main -
+// currently I have not checked it for completeness - it does as much
+// as I need.
+type xlsxF struct {
+	Content string `xml:",chardata"`
+	T       string `xml:"t,attr,omitempty"`   // Formula type
+	Ref     string `xml:"ref,attr,omitempty"` // Shared formula ref
+	Si      int    `xml:"si,attr,omitempty"`  // Shared formula index
 }
 
 // Create a new XLSX Worksheet with default values populated.