|
|
@@ -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.
|