Prechádzať zdrojové kódy

* lib.go: Add functions to convert string coords to cartesian coords - eg "A3" -> 0,2
* lib_test.go: Test coord conversion tools
* workbook.go: hmmm...

Geoffrey J. Teale 14 rokov pred
rodič
commit
1d21c43bbd
3 zmenil súbory, kde vykonal 344 pridanie a 2 odobranie
  1. 131 2
      lib.go
  2. 212 0
      lib_test.go
  3. 1 0
      workbook.go

+ 131 - 2
lib.go

@@ -4,8 +4,10 @@ import (
 	"archive/zip"
 	"fmt"
 	"io"
+	"math"
 	"os"
 	"strconv"
+	"strings"
 	"xml"
 )
 
@@ -58,6 +60,127 @@ type File struct {
 }
 
 
+// getRangeFromString is an internal helper function that converts
+// XLSX internal range syntax to a pair of integers.  For example,
+// the range string "1:3" yield the upper and lower intergers 1 and 3.
+func getRangeFromString(rangeString string) (lower int, upper int, error os.Error) {
+	var parts []string
+	parts = strings.Split(rangeString, ":", 2);
+	if parts[0] == "" {
+		error = os.NewError(fmt.Sprintf("Invalid range '%s'\n", rangeString))
+	}
+	if parts[1] == "" {
+		error = os.NewError(fmt.Sprintf("Invalid range '%s'\n", rangeString))
+	}
+	lower, error = strconv.Atoi(parts[0])
+	if error != nil {
+		error = os.NewError(fmt.Sprintf("Invalid range (not integer in lower bound) %s\n", rangeString))
+	}
+	upper, error = strconv.Atoi(parts[1])
+	if error != nil {
+		error = os.NewError(fmt.Sprintf("Invalid range (not integer in upper bound) %s\n", rangeString))
+	}
+	return lower, upper, error
+}
+
+// positionalLetterMultiplier gives an integer multiplier to use for a
+// position in a letter based column identifer. For example, the
+// column ID "AA" is equivalent to 26*1 + 1, "BA" is equivalent to
+// 26*2 + 1 and "ABA" is equivalent to (676 * 1)+(26 * 2)+1 or
+// ((26**2)*1)+((26**1)*2)+((26**0))*1
+func positionalLetterMultiplier(extent, pos int) int {
+	var result float64
+	var power float64
+	var offset int
+	offset = pos + 1
+	power = float64(extent - offset)
+	result = math.Pow(26, power)
+	return int(result)
+}
+
+
+// lettersToNumeric is used to convert a character based column
+// reference to a zero based numeric column identifier.
+func lettersToNumeric(letters string) int {
+	var sum int = 0 
+	var shift int
+	extent := len(letters)
+	for i, c := range letters {
+		// Just to make life akward.  If we think of this base
+                // 26 notation as being like HEX or binary we hit a
+                // nasty little problem.  The issue is that we have no
+                // 0s and therefore A can be both a 1 and a 0.  The
+                // value range of a letter is different in the most
+                // significant position if (and only if) there is more
+                // than one positions.  For example: 
+		// "A" = 0 
+		//               676 | 26 | 0
+		//               ----+----+----
+                //                 0 |  0 | 0
+		// 
+                //  "Z" = 25
+                //                676 | 26 | 0
+                //                ----+----+----
+                //                  0 |  0 |  25
+                //   "AA" = 26
+                //                676 | 26 | 0
+                //                ----+----+----
+                //                  0 |  1 | 0     <--- note here - the value of "A" maps to both 1 and 0.  
+		if i == 0 && extent > 1 {
+			shift = 1
+		} else {
+			shift = 0
+		}
+		multiplier := positionalLetterMultiplier(extent, i)
+		switch {
+		case 'A' <= c && c <= 'Z':
+			sum += multiplier * ((c - 'A') + shift)
+		case 'a' <= c && c <= 'z':
+			sum += multiplier * ((c - 'a') + shift)
+		}
+	}
+	return sum
+}
+
+
+// letterOnlyMapF is used in conjunction with strings.Map to return
+// only the characters A-Z and a-z in a string
+func letterOnlyMapF(rune int) int {
+	switch {
+	case 'A' <= rune && rune <= 'Z':
+		return rune
+	case 'a' <= rune && rune <= 'z':
+		return rune - 32
+	}
+	return -1
+}
+
+
+// intOnlyMapF is used in conjunction with strings.Map to return only
+// the numeric portions of a string.
+func intOnlyMapF(rune int) int {
+	if rune >= 48 && rune < 58 {
+		return rune
+	}
+	return -1
+}
+
+
+// getCoordsFromCellIDString returns the zero based cartesian
+// coordinates from a cell name in Excel format, e.g. the cellIDString
+// "A1" returns 0, 0 and the "B3" return 1, 2.
+func getCoordsFromCellIDString(cellIDString string) (x, y int, error os.Error) {
+	var letterPart string = strings.Map(letterOnlyMapF, cellIDString)
+	y, error = strconv.Atoi(strings.Map(intOnlyMapF, cellIDString))
+	if error != nil {
+		return x, y, error
+	}
+	y-=1 // Zero based
+	x = lettersToNumeric(letterPart)
+	return x, y, error
+}
+
+
 // readRowsFromSheet is an internal helper function that extracts the
 // rows from a XSLXWorksheet, poulates them with Cells and resolves
 // the value references from the reference table and stores them in
@@ -66,7 +189,12 @@ func readRowsFromSheet(worksheet *XLSXWorksheet, reftable []string) []*Row {
 	rows = make([]*Row, len(worksheet.SheetData.Row))
 	for i, rawrow := range worksheet.SheetData.Row {
 		row := new(Row)
-		row.Cells = make([]*Cell, len(rawrow.C))
+		lower, upper, error := getRangeFromString(rawrow.Spans)
+		if error != nil {
+			panic(error)
+		}
+		size := (upper - lower) + 1
+		row.Cells = make([]*Cell, size)
 		for j, rawcell := range rawrow.C {
 			cell := new(Cell)
 			cell.data = ""
@@ -103,7 +231,7 @@ func readSheetsFromZipFile(f *zip.File, file *File) ([]*Sheet, os.Error) {
 	}
 	sheets := make([]*Sheet, len(workbook.Sheets.Sheet))
 	for i, rawsheet := range workbook.Sheets.Sheet {
-		worksheet, error := getWorksheetFromSheet(rawsheet, file.worksheets)
+		worksheet, error := getWorksheetFromSheet(rawsheet, file.worksheets) // 
 		if error != nil {
 			return nil, error
 		}
@@ -115,6 +243,7 @@ func readSheetsFromZipFile(f *zip.File, file *File) ([]*Sheet, os.Error) {
 }
 
 
+
 // readSharedStringsFromZipFile() is an internal helper function to
 // extract a reference table from the sharedStrings.xml file within
 // the XLSX zip file.

+ 212 - 0
lib_test.go

@@ -4,6 +4,8 @@ package xlsx
 import (
 	"bytes"
 	"os"
+	"strconv"
+	"strings"
 	"testing"
 	"xml"
 )
@@ -153,3 +155,213 @@ func TestReadRowsFromSheet(t *testing.T) {
 	}
 	
 }
+
+
+func TestLettersToNumeric(t *testing.T) {
+	var input string
+	var output int
+
+	input = "A"
+	output = lettersToNumeric(input)
+	if output != 0 {
+		t.Error("Expected output 'A' == 0, but got ", strconv.Itoa(output))
+	}
+	input = "z"
+	output = lettersToNumeric(input)
+	if output != 25 {
+		t.Error("Expected output 'z' == 25, but got ", strconv.Itoa(output))
+	}
+	input = "AA"
+	output = lettersToNumeric(input)
+	if output != 26 {
+		t.Error("Expected output 'AA' == 26, but got ", strconv.Itoa(output))
+	}
+	input = "Az"
+	output = lettersToNumeric(input)
+	if output != 51 {
+		t.Error("Expected output 'Az' == 51, but got ", strconv.Itoa(output))
+	}
+	input = "BA"
+	output = lettersToNumeric(input)
+	if output != 52 {
+		t.Error("Expected output 'BA' == 52, but got ", strconv.Itoa(output))
+	}
+	input = "Bz"
+	output = lettersToNumeric(input)
+	if output != 77 {
+		t.Error("Expected output 'Bz' == 77, but got ", strconv.Itoa(output))
+	}
+	input = "AAA"
+	output = lettersToNumeric(input)
+	if output != 676 {
+		t.Error("Expected output 'AAA' == 676, but got ", strconv.Itoa(output))
+	}
+
+
+}
+
+
+func TestPositionalLetterMultiplier(t *testing.T) {
+	var output int
+	output = positionalLetterMultiplier(1, 0)
+	if output != 1 {
+		t.Error("Expected positionalLetterMultiplier(1, 0) == 1, got ", output)
+	}
+	output = positionalLetterMultiplier(2, 0)
+	if output != 26 {
+		t.Error("Expected positionalLetterMultiplier(2, 0) == 26, got ", output)
+	}
+	output = positionalLetterMultiplier(2, 1)
+	if output != 1 {
+		t.Error("Expected positionalLetterMultiplier(2, 1) == 1, got ", output)
+	}
+	output = positionalLetterMultiplier(3, 0)
+	if output != 676 {
+		t.Error("Expected positionalLetterMultiplier(3, 0) == 676, got ", output)
+	}
+	output = positionalLetterMultiplier(3, 1)
+	if output != 26 {
+		t.Error("Expected positionalLetterMultiplier(3, 1) == 26, got ", output)
+	}
+	output = positionalLetterMultiplier(3, 2)
+	if output != 1 {
+		t.Error("Expected positionalLetterMultiplier(3, 2) == 1, got ", output)
+	}
+}
+
+
+func TestLetterOnlyMapFunction(t *testing.T) {
+	var input string = "ABC123"
+	var output string = strings.Map(letterOnlyMapF, input)
+	if output != "ABC" {
+		t.Error("Expected output == 'ABC' but got ", output)
+	}
+	input = "abc123"
+	output = strings.Map(letterOnlyMapF, input)
+	if output != "ABC" {
+		t.Error("Expected output == 'ABC' but got ", output)
+	}
+}
+
+
+func TestIntOnlyMapFunction(t *testing.T) {
+	var input string = "ABC123"
+	var output string = strings.Map(intOnlyMapF, input)
+	if output != "123" {
+		t.Error("Expected output == '123' but got ", output)
+	}
+}
+
+
+func TestGetCoordsFromCellIDString(t *testing.T) {
+	var cellIDString string = "A3"
+	var x, y int
+	var error os.Error
+	x, y, error = getCoordsFromCellIDString(cellIDString)
+	if error != nil {
+		t.Error(error)
+	}
+	if x != 0 {
+		t.Error("Expected x == 0, but got ", strconv.Itoa(x))
+	}
+	if y != 2 {
+		t.Error("Expected y == 2, but got ", strconv.Itoa(y))
+	}
+}
+
+func TestGetRangeFromString(t *testing.T) {
+	var rangeString string
+	var lower, upper int
+	var error os.Error
+	rangeString = "1:3"
+	lower, upper, error = getRangeFromString(rangeString)
+	if error != nil {
+		t.Error(error)
+	}
+	if lower != 1 {
+		t.Error("Expected lower bound == 1, but got ", strconv.Itoa(lower))
+	}
+	if upper != 3 {
+		t.Error("Expected upper bound == 3, but got ", strconv.Itoa(upper))
+	}
+}
+
+
+// func TestReadRowsFromSheetWithEmptyCells(t *testing.T) {
+// 	var sharedstringsXML = bytes.NewBufferString(`
+// <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+// <sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="8" uniqueCount="5"><si><t>Bob</t></si><si><t>Alice</t></si><si><t>Sue</t></si><si><t>Yes</t></si><si><t>No</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:C3"/><sheetViews><sheetView tabSelected="1" workbookViewId="0"><selection activeCell="D3" sqref="D3"/></sheetView></sheetViews><sheetFormatPr baseColWidth="10" defaultRowHeight="15"/>
+// <sheetData>
+//   <row r="1" spans="1:3">
+//     <c r="A1" t="s">
+//       <v>
+//         0
+//       </v>
+//     </c>
+//     <c r="B1" t="s">
+//       <v>
+//         1
+//       </v>
+//     </c>
+//     <c r="C1" t="s">
+//       <v>
+//         2
+//       </v>
+//     </c>
+//   </row>
+//   <row r="2" spans="1:3">
+//     <c r="A2" t="s">
+//       <v>
+//         3
+//       </v>
+//     </c>
+//     <c r="B2" t="s">
+//       <v>
+//         4
+//       </v>
+//     </c>
+//     <c r="C2" t="s">
+//       <v>
+//         3
+//       </v>
+//     </c>
+//   </row>
+//   <row r="3" spans="1:3">
+//     <c r="A3" t="s">
+//       <v>
+//         4
+//       </v>
+//     </c>
+//     <c r="C3" t="s">
+//       <v>
+//         3
+//       </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)
+// 	error := xml.Unmarshal(sheetxml, worksheet)
+// 	if error != nil {
+// 		t.Error(error.String())
+// 		return
+// 	}
+// 	sst := new(XLSXSST)
+// 	error = xml.Unmarshal(sharedstringsXML, sst)
+// 	if error != nil {
+// 		t.Error(error.String())
+// 		return
+// 	}
+// 	reftable := MakeSharedStringRefTable(sst)
+// 	rows := readRowsFromSheet(worksheet, reftable)
+// 	if len(rows) != 3 {
+// 		t.Error("Expected len(rows) == 3")
+// 	}
+	
+// }

+ 1 - 0
workbook.go

@@ -106,6 +106,7 @@ type XLSXCalcPr struct {
 }
 
 
+
 // getWorksheetFromSheet() is an internal helper function to open a sheetN.xml file, refered to by an xlsx.XLSXSheet struct, from the XLSX file and unmarshal it an xlsx.XLSXWorksheet struct 
 func getWorksheetFromSheet(sheet XLSXSheet, worksheets map[string]*zip.File) (*XLSXWorksheet, os.Error) {
 	var rc io.ReadCloser