소스 검색

Merge remote-tracking branch 'tealeg/master'

blackss2 10 년 전
부모
커밋
2b26e0f34f
16개의 변경된 파일599개의 추가작업 그리고 98개의 파일을 삭제
  1. 1 1
      README.org
  2. 38 25
      cell.go
  3. 21 0
      cell_test.go
  4. 1 1
      file_test.go
  5. 244 0
      fuzzy_test.go
  6. 38 11
      lib.go
  7. 129 15
      lib_test.go
  8. 16 8
      sheet.go
  9. 28 1
      sheet_test.go
  10. 40 15
      style.go
  11. 4 4
      style_test.go
  12. BIN
      testdocs/badfile_noWorkbookRels.xlsx
  13. BIN
      testdocs/badfile_noWorksheets.xlsx
  14. 31 10
      xmlStyle.go
  15. 1 1
      xmlStyle_test.go
  16. 7 6
      xmlWorksheet.go

+ 1 - 1
README.org

@@ -98,7 +98,7 @@ This code is under a BSD style license:
   PROVIDED BY Geoffrey Teale ``AS IS'' AND ANY EXPRESS OR IMPLIED
   PROVIDED BY Geoffrey Teale ``AS IS'' AND ANY EXPRESS OR IMPLIED
   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
   MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-  DISCLAIMED. IN NO EVENT SHALL THE FREEBSD PROJECT OR CONTRIBUTORS BE
+  DISCLAIMED. IN NO EVENT SHALL GEOFFREY TEALE OR CONTRIBUTORS BE
   LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
   LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
   SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
   SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR

+ 38 - 25
cell.go

@@ -275,33 +275,34 @@ func (c *Cell) GetNumberFormat() string {
 	return c.numFmt
 	return c.numFmt
 }
 }
 
 
-func (c *Cell) formatToFloat(format string) string {
+func (c *Cell) formatToFloat(format string) (string, error) {
 	f, err := strconv.ParseFloat(c.Value, 64)
 	f, err := strconv.ParseFloat(c.Value, 64)
 	if err != nil {
 	if err != nil {
-		return err.Error()
+		return c.Value, err
 	}
 	}
-	return fmt.Sprintf(format, f)
+	return fmt.Sprintf(format, f), nil
 }
 }
 
 
-func (c *Cell) formatToInt(format string) string {
+func (c *Cell) formatToInt(format string) (string, error) {
 	f, err := strconv.ParseFloat(c.Value, 64)
 	f, err := strconv.ParseFloat(c.Value, 64)
 	if err != nil {
 	if err != nil {
-		return err.Error()
+		return c.Value, err
 	}
 	}
-	return fmt.Sprintf(format, int(f))
+	return fmt.Sprintf(format, int(f)), nil
 }
 }
 
 
-// FormattedValue returns the formatted version of the value.
-// If it's a string type, c.Value will just be returned. Otherwise,
-// it will attempt to apply Excel formatting to the value.
-func (c *Cell) FormattedValue() string {
+// SafeFormattedValue returns a value, and possibly an error condition
+// from a Cell.  If it is possible to apply a format to the cell
+// value, it will do so, if not then an error will be returned, along
+// with the raw value of the Cell.
+func (c *Cell) SafeFormattedValue() (string, error) {
 	var numberFormat = c.GetNumberFormat()
 	var numberFormat = c.GetNumberFormat()
 	if isTimeFormat(numberFormat) {
 	if isTimeFormat(numberFormat) {
 		return parseTime(c)
 		return parseTime(c)
 	}
 	}
 	switch numberFormat {
 	switch numberFormat {
 	case builtInNumFmt[builtInNumFmtIndex_GENERAL], builtInNumFmt[builtInNumFmtIndex_STRING]:
 	case builtInNumFmt[builtInNumFmtIndex_GENERAL], builtInNumFmt[builtInNumFmtIndex_STRING]:
-		return c.Value
+		return c.Value, nil
 	case builtInNumFmt[builtInNumFmtIndex_INT], "#,##0":
 	case builtInNumFmt[builtInNumFmtIndex_INT], "#,##0":
 		return c.formatToInt("%d")
 		return c.formatToInt("%d")
 	case builtInNumFmt[builtInNumFmtIndex_FLOAT], "#,##0.00":
 	case builtInNumFmt[builtInNumFmtIndex_FLOAT], "#,##0.00":
@@ -309,48 +310,60 @@ func (c *Cell) FormattedValue() string {
 	case "#,##0 ;(#,##0)", "#,##0 ;[red](#,##0)":
 	case "#,##0 ;(#,##0)", "#,##0 ;[red](#,##0)":
 		f, err := strconv.ParseFloat(c.Value, 64)
 		f, err := strconv.ParseFloat(c.Value, 64)
 		if err != nil {
 		if err != nil {
-			return err.Error()
+			return c.Value, err
 		}
 		}
 		if f < 0 {
 		if f < 0 {
 			i := int(math.Abs(f))
 			i := int(math.Abs(f))
-			return fmt.Sprintf("(%d)", i)
+			return fmt.Sprintf("(%d)", i), nil
 		}
 		}
 		i := int(f)
 		i := int(f)
-		return fmt.Sprintf("%d", i)
+		return fmt.Sprintf("%d", i), nil
 	case "#,##0.00;(#,##0.00)", "#,##0.00;[red](#,##0.00)":
 	case "#,##0.00;(#,##0.00)", "#,##0.00;[red](#,##0.00)":
 		f, err := strconv.ParseFloat(c.Value, 64)
 		f, err := strconv.ParseFloat(c.Value, 64)
 		if err != nil {
 		if err != nil {
-			return err.Error()
+			return c.Value, err
 		}
 		}
 		if f < 0 {
 		if f < 0 {
-			return fmt.Sprintf("(%.2f)", f)
+			return fmt.Sprintf("(%.2f)", f), nil
 		}
 		}
-		return fmt.Sprintf("%.2f", f)
+		return fmt.Sprintf("%.2f", f), nil
 	case "0%":
 	case "0%":
 		f, err := strconv.ParseFloat(c.Value, 64)
 		f, err := strconv.ParseFloat(c.Value, 64)
 		if err != nil {
 		if err != nil {
-			return err.Error()
+			return c.Value, err
 		}
 		}
 		f = f * 100
 		f = f * 100
-		return fmt.Sprintf("%d%%", int(f))
+		return fmt.Sprintf("%d%%", int(f)), nil
 	case "0.00%":
 	case "0.00%":
 		f, err := strconv.ParseFloat(c.Value, 64)
 		f, err := strconv.ParseFloat(c.Value, 64)
 		if err != nil {
 		if err != nil {
-			return err.Error()
+			return c.Value, err
 		}
 		}
 		f = f * 100
 		f = f * 100
-		return fmt.Sprintf("%.2f%%", f)
+		return fmt.Sprintf("%.2f%%", f), nil
 	case "0.00e+00", "##0.0e+0":
 	case "0.00e+00", "##0.0e+0":
 		return c.formatToFloat("%e")
 		return c.formatToFloat("%e")
 	}
 	}
-	return c.Value
+	return c.Value, nil
+
+}
+
+// FormattedValue returns the formatted version of the value.
+// If it's a string type, c.Value will just be returned. Otherwise,
+// it will attempt to apply Excel formatting to the value.
+func (c *Cell) FormattedValue() string {
+	value, err := c.SafeFormattedValue()
+	if err != nil {
+		return err.Error()
+	}
+	return value
 }
 }
 
 
 // parseTime returns a string parsed using time.Time
 // parseTime returns a string parsed using time.Time
-func parseTime(c *Cell) string {
+func parseTime(c *Cell) (string, error) {
 	f, err := strconv.ParseFloat(c.Value, 64)
 	f, err := strconv.ParseFloat(c.Value, 64)
 	if err != nil {
 	if err != nil {
-		return err.Error()
+		return c.Value, err
 	}
 	}
 	val := TimeFromExcelTime(f, c.date1904)
 	val := TimeFromExcelTime(f, c.date1904)
 	format := c.GetNumberFormat()
 	format := c.GetNumberFormat()
@@ -389,7 +402,7 @@ func parseTime(c *Cell) string {
 		format = strings.Replace(format, "[3]", "3", 1)
 		format = strings.Replace(format, "[3]", "3", 1)
 		format = strings.Replace(format, "[15]", "15", 1)
 		format = strings.Replace(format, "[15]", "15", 1)
 	}
 	}
-	return val.Format(format)
+	return val.Format(format), nil
 }
 }
 
 
 // isTimeFormat checks whether an Excel format string represents
 // isTimeFormat checks whether an Excel format string represents

+ 21 - 0
cell_test.go

@@ -114,8 +114,29 @@ func (l *CellSuite) TestSetFloat(c *C) {
 	c.Assert(cell.Value, Equals, "37947.75334343")
 	c.Assert(cell.Value, Equals, "37947.75334343")
 }
 }
 
 
+// SafeFormattedValue returns an error for formatting errors
+func (l *CellSuite) TestSafeFormattedValueErrorsOnBadFormat(c *C) {
+	cell := Cell{Value: "Fudge Cake"}
+	cell.numFmt = "#,##0 ;(#,##0)"
+	value, err := cell.SafeFormattedValue()
+	c.Assert(value, Equals, "Fudge Cake")
+	c.Assert(err, NotNil)
+	c.Assert(err.Error(), Equals, "strconv.ParseFloat: parsing \"Fudge Cake\": invalid syntax")
+}
+
+// FormattedValue returns a string containing error text for formatting errors
+func (l *CellSuite) TestFormattedValueReturnsErrorAsValueForBadFormat(c *C) {
+	cell := Cell{Value: "Fudge Cake"}
+	cell.numFmt = "#,##0 ;(#,##0)"
+	value := cell.FormattedValue()
+	c.Assert(value, Equals, "strconv.ParseFloat: parsing \"Fudge Cake\": invalid syntax")
+}
+
 // We can return a string representation of the formatted data
 // We can return a string representation of the formatted data
 func (l *CellSuite) TestFormattedValue(c *C) {
 func (l *CellSuite) TestFormattedValue(c *C) {
+	// XXX TODO, this test should probably be split down, and made
+	// in terms of SafeFormattedValue, as FormattedValue wraps
+	// that function now.
 	cell := Cell{Value: "37947.7500001"}
 	cell := Cell{Value: "37947.7500001"}
 	negativeCell := Cell{Value: "-37947.7500001"}
 	negativeCell := Cell{Value: "-37947.7500001"}
 	smallCell := Cell{Value: "0.007"}
 	smallCell := Cell{Value: "0.007"}

+ 1 - 1
file_test.go

@@ -659,7 +659,7 @@ func (l *FileSuite) TestMarshalFile(c *C) {
 	// For now we only allow simple string data in the
 	// For now we only allow simple string data in the
 	// spreadsheet.  Style support will follow.
 	// spreadsheet.  Style support will follow.
 	expectedStyles := `<?xml version="1.0" encoding="UTF-8"?>
 	expectedStyles := `<?xml version="1.0" encoding="UTF-8"?>
-<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><fonts count="1"><font><sz val="12"/><name val="Verdana"/><family val="0"/><charset val="0"/></font></fonts><fills count="2"><fill><patternFill patternType="none"><fgColor rgb="FFFFFFFF"/><bgColor rgb="00000000"/></patternFill></fill><fill><patternFill patternType="lightGrey"/></fill></fills><borders count="1"><border><left style="none"/><right style="none"/><top style="none"/><bottom style="none"/></border></borders><cellStyleXfs count="1"><xf applyAlignment="0" applyBorder="0" applyFont="0" applyFill="0" applyNumberFormat="0" applyProtection="0" borderId="0" fillId="0" fontId="0" numFmtId="0"><alignment horizontal="" indent="0" shrinkToFit="0" textRotation="0" vertical="" wrapText="0"/></xf></cellStyleXfs><cellXfs count="1"><xf applyAlignment="0" applyBorder="0" applyFont="0" applyFill="0" applyNumberFormat="0" applyProtection="0" borderId="0" fillId="0" fontId="0" numFmtId="0"><alignment horizontal="" indent="0" shrinkToFit="0" textRotation="0" vertical="" wrapText="0"/></xf></cellXfs></styleSheet>`
+<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><fonts count="1"><font><sz val="12"/><name val="Verdana"/><family val="0"/><charset val="0"/></font></fonts><fills count="2"><fill><patternFill patternType="none"><fgColor rgb="FFFFFFFF"/><bgColor rgb="00000000"/></patternFill></fill><fill><patternFill patternType="lightGray"/></fill></fills><borders count="1"><border><left style="none"></left><right style="none"></right><top style="none"></top><bottom style="none"></bottom></border></borders><cellStyleXfs count="1"><xf applyAlignment="0" applyBorder="0" applyFont="0" applyFill="0" applyNumberFormat="0" applyProtection="0" borderId="0" fillId="0" fontId="0" numFmtId="0"><alignment horizontal="" indent="0" shrinkToFit="0" textRotation="0" vertical="" wrapText="0"/></xf></cellStyleXfs><cellXfs count="1"><xf applyAlignment="0" applyBorder="0" applyFont="0" applyFill="0" applyNumberFormat="0" applyProtection="0" borderId="0" fillId="0" fontId="0" numFmtId="0"><alignment horizontal="" indent="0" shrinkToFit="0" textRotation="0" vertical="" wrapText="0"/></xf></cellXfs></styleSheet>`
 	c.Assert(parts["xl/styles.xml"], Equals, expectedStyles)
 	c.Assert(parts["xl/styles.xml"], Equals, expectedStyles)
 }
 }
 
 

+ 244 - 0
fuzzy_test.go

@@ -0,0 +1,244 @@
+// +build fuzzy
+
+package xlsx
+
+import (
+	"archive/zip"
+	"bytes"
+	"encoding/xml"
+	"flag"
+	"fmt"
+	"io"
+	"log"
+	"math/rand"
+	"path/filepath"
+	"reflect"
+	"strconv"
+	"testing"
+	"time"
+
+	. "gopkg.in/check.v1"
+)
+
+type Fuzzy struct{}
+
+var _ = Suite(&Fuzzy{})
+var randseed *int64 = flag.Int64("test.seed", time.Now().Unix(), "Set the random seed of the test for repeatable results")
+
+type tokenchange struct {
+	file bytes.Buffer
+	old  xml.Token
+	new  xml.Token
+}
+
+type filechange struct {
+	File *zip.Reader
+	Name string
+	Old  xml.Token
+	New  xml.Token
+}
+
+var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
+var numbers = []rune("0123456789")
+
+func randString(n int) []byte {
+	b := make([]rune, n)
+	for i := range b {
+		b[i] = letters[rand.Intn(len(letters))]
+	}
+	return []byte(string(b))
+}
+
+func randInt(n int) []byte {
+	b := make([]rune, n)
+	for i := range b {
+		b[i] = numbers[rand.Intn(len(numbers))]
+	}
+	return []byte(string(b))
+}
+
+//This function creates variations on tokens without regards as to positions in the file.
+func getTokenVariations(t xml.Token) []xml.Token {
+	var result []xml.Token = make([]xml.Token, 0)
+	switch t := t.(type) {
+	case xml.CharData:
+		{
+			//If the token is a number try some random number
+			if _, err := strconv.Atoi(string(t)); err == nil {
+				result = append(result, xml.CharData(randInt(rand.Intn(15))))
+			}
+
+			result = append(result, xml.CharData(randString(rand.Intn(100))))
+			return result
+		}
+	case xml.StartElement:
+		{
+			for k := range t.Attr {
+				if _, err := strconv.Atoi(string(t.Attr[k].Value)); err == nil {
+					start := xml.CopyToken(t).(xml.StartElement)
+					start.Attr[k].Value = string(randInt(rand.Intn(15)))
+					result = append(result, start)
+				}
+				start := xml.CopyToken(t).(xml.StartElement)
+				start.Attr[k].Value = string(randString(rand.Intn(100)))
+				result = append(result, start)
+			}
+			return result
+		}
+
+	default:
+		{
+			return make([]xml.Token, 0) // No variations on non char tokens yet
+		}
+	}
+}
+
+func variationsXML(f *zip.File) chan tokenchange {
+	result := make(chan tokenchange)
+	r, _ := f.Open()
+	xmlReader := xml.NewDecoder(r)
+	var tokenList []xml.Token
+	for {
+		if t, err := xmlReader.Token(); err == nil {
+			tokenList = append(tokenList, xml.CopyToken(t))
+		} else {
+			break
+		}
+	}
+
+	go func() {
+		//Over every token we want to break
+		for TokenToBreak, _ := range tokenList {
+			//Get the ways we can break that token
+			for _, brokenToken := range getTokenVariations(tokenList[TokenToBreak]) {
+				var buf bytes.Buffer
+				xmlWriter := xml.NewEncoder(&buf)
+				//Now create an xml file where one token is broken
+				for currentToken, t := range tokenList {
+					if currentToken == TokenToBreak {
+						xmlWriter.EncodeToken(brokenToken)
+					} else {
+						xmlWriter.EncodeToken(t)
+					}
+				}
+				xmlWriter.Flush()
+				result <- tokenchange{buf, tokenList[TokenToBreak], brokenToken}
+			}
+		}
+		close(result)
+	}()
+	return result
+}
+
+func generateBrokenFiles(r *zip.Reader) chan filechange {
+	result := make(chan filechange)
+	go func() {
+		count := 0
+		//For every file in the zip we want variation on
+		for breakIndex, fileToBreak := range r.File {
+			if filepath.Ext(fileToBreak.Name) != ".xml" {
+				continue //We cannot create variations on non-xml files
+			}
+
+			variationCount := 0
+			//For every broken version of that file
+			for changedFile := range variationsXML(fileToBreak) {
+				variationCount++
+				var buffer bytes.Buffer
+				//Create a new xlsx file in memory
+				outZip := zip.NewWriter(&buffer)
+				w, err := outZip.Create(fileToBreak.Name)
+				if err != nil {
+					log.Fatal(err)
+				}
+				//Add modified file to xlsx
+				_, err = changedFile.file.WriteTo(w)
+				if err != nil {
+					log.Fatal("changedFile.file.WriteTo", err)
+				}
+				//Add other, unchanged, files.
+				for otherIndex, otherFile := range r.File {
+					if breakIndex == otherIndex {
+						continue
+					}
+					to, err := outZip.Create(otherFile.Name)
+					if err != nil {
+						log.Fatal("Could not add new file to xlsx due to", err)
+					}
+					from, err := otherFile.Open()
+					if err != nil {
+						log.Fatal("Could not open original file from template xlsx due to", err)
+					}
+					io.Copy(to, from)
+					from.Close()
+				}
+				outZip.Close()
+
+				//Return this combination of broken files
+				b := buffer.Bytes()
+				var res filechange
+				res.File, _ = zip.NewReader(bytes.NewReader(b), int64(len(b)))
+				res.Name = fileToBreak.Name
+				res.Old = changedFile.old
+				res.New = changedFile.new
+				result <- res
+				count++
+			}
+		}
+		close(result)
+	}()
+	return result
+}
+
+func Raises(f func()) (err interface{}) {
+	defer func() {
+		err = recover()
+	}()
+	err = nil
+	f()
+	return
+}
+
+func tokenToString(t xml.Token) string {
+	switch t := t.(type) {
+	case xml.CharData:
+		{
+			return string(t)
+		}
+	default:
+		{
+			return fmt.Sprint(t)
+		}
+	}
+}
+
+func (f *Fuzzy) TestRandomBrokenParts(c *C) {
+	if testing.Short() {
+		c.Log("This test, tests many versions of an xlsx file and might take a while, it is being skipped")
+		c.SucceedNow()
+	}
+	log.Println("Fuzzy test is using this -test.seed=" + strconv.FormatInt(*randseed, 10))
+	rand.Seed(*randseed)
+	template, err := zip.OpenReader("./testdocs/testfile.xlsx")
+	c.Assert(err, IsNil)
+	defer template.Close()
+
+	count := 0
+
+	for brokenFile := range generateBrokenFiles(&template.Reader) {
+		count++
+		if testing.Verbose() {
+			//If the library panics fatally it would be nice to know why
+			log.Println("Testing change to ", brokenFile.Name, " on token ", tokenToString(brokenFile.Old), " of type ", reflect.TypeOf(brokenFile.Old), " to ", tokenToString(brokenFile.New))
+		}
+
+		if e := Raises(func() { ReadZipReader(brokenFile.File) }); e != nil {
+
+			c.Log("Some file with random changes did raise an exception instead of returning an error", e)
+			c.Log("Testing change to ", brokenFile.Name, " on token ", tokenToString(brokenFile.Old), " of type ", reflect.TypeOf(brokenFile.Old), " to ", tokenToString(brokenFile.New))
+			c.FailNow()
+		}
+
+	}
+	c.Succeed()
+}

+ 38 - 11
lib.go

@@ -17,8 +17,8 @@ type XLSXReaderError struct {
 	Err string
 	Err string
 }
 }
 
 
-// String() returns a string value from an XLSXReaderError struct in
-// order that it might comply with the os.Error interface.
+// Error returns a string value from an XLSXReaderError struct in order
+// that it might comply with the builtin.error interface.
 func (e *XLSXReaderError) Error() string {
 func (e *XLSXReaderError) Error() string {
 	return e.Err
 	return e.Err
 }
 }
@@ -251,13 +251,14 @@ func calculateMaxMinFromWorksheet(worksheet *xlsxWorksheet) (minx, miny, maxx, m
 // return an empty Row large enough to encompass that span and
 // return an empty Row large enough to encompass that span and
 // populate it with empty cells.  All rows start from cell 1 -
 // populate it with empty cells.  All rows start from cell 1 -
 // regardless of the lower bound of the span.
 // regardless of the lower bound of the span.
-func makeRowFromSpan(spans string) *Row {
+func makeRowFromSpan(spans string, sheet *Sheet) *Row {
 	var error error
 	var error error
 	var upper int
 	var upper int
 	var row *Row
 	var row *Row
 	var cell *Cell
 	var cell *Cell
 
 
 	row = new(Row)
 	row = new(Row)
+	row.Sheet = sheet
 	_, upper, error = getRangeFromString(spans)
 	_, upper, error = getRangeFromString(spans)
 	if error != nil {
 	if error != nil {
 		panic(error)
 		panic(error)
@@ -273,12 +274,13 @@ func makeRowFromSpan(spans string) *Row {
 }
 }
 
 
 // makeRowFromRaw returns the Row representation of the xlsxRow.
 // makeRowFromRaw returns the Row representation of the xlsxRow.
-func makeRowFromRaw(rawrow xlsxRow) *Row {
+func makeRowFromRaw(rawrow xlsxRow, sheet *Sheet) *Row {
 	var upper int
 	var upper int
 	var row *Row
 	var row *Row
 	var cell *Cell
 	var cell *Cell
 
 
 	row = new(Row)
 	row = new(Row)
+	row.Sheet = sheet
 	upper = -1
 	upper = -1
 
 
 	for _, rawcell := range rawrow.C {
 	for _, rawcell := range rawrow.C {
@@ -305,9 +307,10 @@ func makeRowFromRaw(rawrow xlsxRow) *Row {
 	return row
 	return row
 }
 }
 
 
-func makeEmptyRow() *Row {
+func makeEmptyRow(sheet *Sheet) *Row {
 	row := new(Row)
 	row := new(Row)
 	row.Cells = make([]*Cell, 0)
 	row.Cells = make([]*Cell, 0)
+	row.Sheet = sheet
 	return row
 	return row
 }
 }
 
 
@@ -417,7 +420,7 @@ func fillCellData(rawcell xlsxC, reftable *RefTable, sharedFormulas map[int]shar
 // rows from a XSLXWorksheet, populates them with Cells and resolves
 // rows from a XSLXWorksheet, populates them with Cells and resolves
 // the value references from the reference table and stores them in
 // the value references from the reference table and stores them in
 // the rows and columns.
 // the rows and columns.
-func readRowsFromSheet(Worksheet *xlsxWorksheet, file *File) ([]*Row, []*Col, int, int) {
+func readRowsFromSheet(Worksheet *xlsxWorksheet, file *File, sheet *Sheet) ([]*Row, []*Col, int, int) {
 	var rows []*Row
 	var rows []*Row
 	var cols []*Col
 	var cols []*Col
 	var row *Row
 	var row *Row
@@ -439,6 +442,7 @@ func readRowsFromSheet(Worksheet *xlsxWorksheet, file *File) ([]*Row, []*Col, in
 	if err != nil {
 	if err != nil {
 		panic(err.Error())
 		panic(err.Error())
 	}
 	}
+
 	rowCount = maxRow + 1
 	rowCount = maxRow + 1
 	colCount = maxCol + 1
 	colCount = maxCol + 1
 	rows = make([]*Row, rowCount)
 	rows = make([]*Row, rowCount)
@@ -473,23 +477,27 @@ func readRowsFromSheet(Worksheet *xlsxWorksheet, file *File) ([]*Row, []*Col, in
 
 
 	// insert leading empty rows that is in front of minRow
 	// insert leading empty rows that is in front of minRow
 	for rowIndex := 0; rowIndex < minRow; rowIndex++ {
 	for rowIndex := 0; rowIndex < minRow; rowIndex++ {
-		rows[rowIndex] = makeEmptyRow()
+		rows[rowIndex] = makeEmptyRow(sheet)
 	}
 	}
 
 
+	numRows := len(rows)
 	for rowIndex := 0; rowIndex < len(Worksheet.SheetData.Row); rowIndex++ {
 	for rowIndex := 0; rowIndex < len(Worksheet.SheetData.Row); rowIndex++ {
 		rawrow := Worksheet.SheetData.Row[rowIndex]
 		rawrow := Worksheet.SheetData.Row[rowIndex]
 		// Some spreadsheets will omit blank rows from the
 		// Some spreadsheets will omit blank rows from the
 		// stored data
 		// stored data
 		for rawrow.R > (insertRowIndex + 1) {
 		for rawrow.R > (insertRowIndex + 1) {
 			// Put an empty Row into the array
 			// Put an empty Row into the array
-			rows[insertRowIndex-minRow] = makeEmptyRow()
+			index := insertRowIndex - minRow
+			if index < numRows {
+				rows[index] = makeEmptyRow(sheet)
+			}
 			insertRowIndex++
 			insertRowIndex++
 		}
 		}
 		// range is not empty and only one range exist
 		// range is not empty and only one range exist
 		if len(rawrow.Spans) != 0 && strings.Count(rawrow.Spans, ":") == 1 {
 		if len(rawrow.Spans) != 0 && strings.Count(rawrow.Spans, ":") == 1 {
-			row = makeRowFromSpan(rawrow.Spans)
+			row = makeRowFromSpan(rawrow.Spans, sheet)
 		} else {
 		} else {
-			row = makeRowFromRaw(rawrow)
+			row = makeRowFromRaw(rawrow, sheet)
 		}
 		}
 
 
 		row.Hidden = rawrow.Hidden
 		row.Hidden = rawrow.Hidden
@@ -559,6 +567,19 @@ func readSheetViews(xSheetViews xlsxSheetViews) []SheetView {
 // sheet and get the results back on the provided channel.
 // sheet and get the results back on the provided channel.
 func readSheetFromFile(sc chan *indexedSheet, index int, rsheet xlsxSheet, fi *File, sheetXMLMap map[string]string) {
 func readSheetFromFile(sc chan *indexedSheet, index int, rsheet xlsxSheet, fi *File, sheetXMLMap map[string]string) {
 	result := &indexedSheet{Index: index, Sheet: nil, Error: nil}
 	result := &indexedSheet{Index: index, Sheet: nil, Error: nil}
+	defer func() {
+		if e := recover(); e != nil {
+			switch e.(type) {
+			case error:
+				result.Error = e.(error)
+			default:
+				result.Error = errors.New("unexpected error")
+			}
+			// The only thing here, is if one close the channel. but its not the case
+			sc <- result
+		}
+	}()
+
 	worksheet, error := getWorksheetFromSheet(rsheet, fi.worksheets, sheetXMLMap)
 	worksheet, error := getWorksheetFromSheet(rsheet, fi.worksheets, sheetXMLMap)
 	if error != nil {
 	if error != nil {
 		result.Error = error
 		result.Error = error
@@ -567,7 +588,7 @@ func readSheetFromFile(sc chan *indexedSheet, index int, rsheet xlsxSheet, fi *F
 	}
 	}
 	sheet := new(Sheet)
 	sheet := new(Sheet)
 	sheet.File = fi
 	sheet.File = fi
-	sheet.Rows, sheet.Cols, sheet.MaxCol, sheet.MaxRow = readRowsFromSheet(worksheet, fi)
+	sheet.Rows, sheet.Cols, sheet.MaxCol, sheet.MaxRow = readRowsFromSheet(worksheet, fi, sheet)
 	sheet.Hidden = rsheet.State == sheetStateHidden || rsheet.State == sheetStateVeryHidden
 	sheet.Hidden = rsheet.State == sheetStateHidden || rsheet.State == sheetStateVeryHidden
 	sheet.SheetViews = readSheetViews(worksheet.SheetViews)
 	sheet.SheetViews = readSheetViews(worksheet.SheetViews)
 
 
@@ -828,10 +849,16 @@ func ReadZipReader(r *zip.Reader) (*File, error) {
 			}
 			}
 		}
 		}
 	}
 	}
+	if workbookRels == nil {
+		return nil, fmt.Errorf("xl/_rels/workbook.xml.rels not found in input xlsx.")
+	}
 	sheetXMLMap, err = readWorkbookRelationsFromZipFile(workbookRels)
 	sheetXMLMap, err = readWorkbookRelationsFromZipFile(workbookRels)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
+	if len(worksheets) == 0 {
+		return nil, fmt.Errorf("Input xlsx contains no worksheets.")
+	}
 	file.worksheets = worksheets
 	file.worksheets = worksheets
 	reftable, err = readSharedStringsFromZipFile(sharedStrings)
 	reftable, err = readSharedStringsFromZipFile(sharedStrings)
 	if err != nil {
 	if err != nil {

+ 129 - 15
lib_test.go

@@ -13,6 +13,20 @@ type LibSuite struct{}
 
 
 var _ = Suite(&LibSuite{})
 var _ = Suite(&LibSuite{})
 
 
+// Attempting to open a file without workbook.xml.rels returns an error.
+func (l *LibSuite) TestReadZipReaderWithFileWithNoWorkbookRels(c *C) {
+	_, err := OpenFile("./testdocs/badfile_noWorkbookRels.xlsx")
+	c.Assert(err, NotNil)
+	c.Assert(err.Error(), Equals, "xl/_rels/workbook.xml.rels not found in input xlsx.")
+}
+
+// Attempting to open a file with no worksheets returns an error.
+func (l *LibSuite) TestReadZipReaderWithFileWithNoWorksheets(c *C) {
+	_, err := OpenFile("./testdocs/badfile_noWorksheets.xlsx")
+	c.Assert(err, NotNil)
+	c.Assert(err.Error(), Equals, "Input xlsx contains no worksheets.")
+}
+
 // which they are contained from the XLSX file, even when the
 // which they are contained from the XLSX file, even when the
 // worksheet files have arbitrary, non-numeric names.
 // worksheet files have arbitrary, non-numeric names.
 func (l *LibSuite) TestReadWorkbookRelationsFromZipFileWithFunnyNames(c *C) {
 func (l *LibSuite) TestReadWorkbookRelationsFromZipFileWithFunnyNames(c *C) {
@@ -225,18 +239,23 @@ func (l *LibSuite) TestMakeRowFromSpan(c *C) {
 	var rangeString string
 	var rangeString string
 	var row *Row
 	var row *Row
 	var length int
 	var length int
+	var sheet *Sheet
+	sheet = new(Sheet)
 	rangeString = "1:3"
 	rangeString = "1:3"
-	row = makeRowFromSpan(rangeString)
+	row = makeRowFromSpan(rangeString, sheet)
 	length = len(row.Cells)
 	length = len(row.Cells)
 	c.Assert(length, Equals, 3)
 	c.Assert(length, Equals, 3)
+	c.Assert(row.Sheet, Equals, sheet)
 	rangeString = "5:7" // Note - we ignore lower bound!
 	rangeString = "5:7" // Note - we ignore lower bound!
-	row = makeRowFromSpan(rangeString)
+	row = makeRowFromSpan(rangeString, sheet)
 	length = len(row.Cells)
 	length = len(row.Cells)
 	c.Assert(length, Equals, 7)
 	c.Assert(length, Equals, 7)
+	c.Assert(row.Sheet, Equals, sheet)
 	rangeString = "1:1"
 	rangeString = "1:1"
-	row = makeRowFromSpan(rangeString)
+	row = makeRowFromSpan(rangeString, sheet)
 	length = len(row.Cells)
 	length = len(row.Cells)
 	c.Assert(length, Equals, 1)
 	c.Assert(length, Equals, 1)
+	c.Assert(row.Sheet, Equals, sheet)
 }
 }
 
 
 func (l *LibSuite) TestReadRowsFromSheet(c *C) {
 func (l *LibSuite) TestReadRowsFromSheet(c *C) {
@@ -300,10 +319,12 @@ func (l *LibSuite) TestReadRowsFromSheet(c *C) {
 	c.Assert(err, IsNil)
 	c.Assert(err, IsNil)
 	file := new(File)
 	file := new(File)
 	file.referenceTable = MakeSharedStringRefTable(sst)
 	file.referenceTable = MakeSharedStringRefTable(sst)
-	rows, cols, maxCols, maxRows := readRowsFromSheet(worksheet, file)
+	sheet := new(Sheet)
+	rows, cols, maxCols, maxRows := readRowsFromSheet(worksheet, file, sheet)
 	c.Assert(maxRows, Equals, 2)
 	c.Assert(maxRows, Equals, 2)
 	c.Assert(maxCols, Equals, 2)
 	c.Assert(maxCols, Equals, 2)
 	row := rows[0]
 	row := rows[0]
+	c.Assert(row.Sheet, Equals, sheet)
 	c.Assert(len(row.Cells), Equals, 2)
 	c.Assert(len(row.Cells), Equals, 2)
 	cell1 := row.Cells[0]
 	cell1 := row.Cells[0]
 	c.Assert(cell1.Value, Equals, "Foo")
 	c.Assert(cell1.Value, Equals, "Foo")
@@ -321,6 +342,79 @@ func (l *LibSuite) TestReadRowsFromSheet(c *C) {
 	c.Assert(pane.YSplit, Equals, 1.0)
 	c.Assert(pane.YSplit, Equals, 1.0)
 }
 }
 
 
+// An invalid value in the "r" attribute in a <row> was causing a panic
+// in readRowsFromSheet. This test is a copy of TestReadRowsFromSheet,
+// with the important difference of the value 1048576 below in <row r="1048576", which is
+// higher than the number of rows in the sheet. That number itself isn't significant;
+// it just happens to be the value found to trigger the error in a user's file.
+func (l *LibSuite) TestReadRowsFromSheetBadR(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>Foo</t>
+  </si>
+  <si>
+    <t>Bar</t>
+  </si>
+  <si>
+    <t xml:space="preserve">Baz </t>
+  </si>
+  <si>
+    <t>Quuk</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:B2"/>
+  <sheetViews>
+    <sheetView tabSelected="1" workbookViewId="0">
+      <selection activeCell="C2" sqref="C2"/>
+	  <pane ySplit="1" topLeftCell="A2" activePane="bottomLeft" state="frozen"/>
+    </sheetView>
+  </sheetViews>
+  <sheetFormatPr baseColWidth="10" defaultRowHeight="15"/>
+  <sheetData>
+    <row r="1" spans="1:2">
+      <c r="A1" t="s">
+        <v>0</v>
+      </c>
+      <c r="B1" t="s">
+        <v>1</v>
+      </c>
+    </row>
+    <row r="1048576" spans="1:2">
+      <c r="A2" t="s">
+        <v>2</v>
+      </c>
+      <c r="B2" 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)
+	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)
+
+	sheet := new(Sheet)
+	// Discarding all return values; this test is a regression for
+	// a panic due to an "index out of range."
+	readRowsFromSheet(worksheet, file, sheet)
+}
+
 func (l *LibSuite) TestReadRowsFromSheetWithLeadingEmptyRows(c *C) {
 func (l *LibSuite) TestReadRowsFromSheetWithLeadingEmptyRows(c *C) {
 	var sharedstringsXML = bytes.NewBufferString(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
 	var sharedstringsXML = bytes.NewBufferString(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
 <sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="2" uniqueCount="2"><si><t>ABC</t></si><si><t>DEF</t></si></sst>`)
 <sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="2" uniqueCount="2"><si><t>ABC</t></si><si><t>DEF</t></si></sst>`)
@@ -363,7 +457,8 @@ func (l *LibSuite) TestReadRowsFromSheetWithLeadingEmptyRows(c *C) {
 
 
 	file := new(File)
 	file := new(File)
 	file.referenceTable = MakeSharedStringRefTable(sst)
 	file.referenceTable = MakeSharedStringRefTable(sst)
-	rows, _, maxCols, maxRows := readRowsFromSheet(worksheet, file)
+	sheet := new(Sheet)
+	rows, _, maxCols, maxRows := readRowsFromSheet(worksheet, file, sheet)
 	c.Assert(maxRows, Equals, 5)
 	c.Assert(maxRows, Equals, 5)
 	c.Assert(maxCols, Equals, 1)
 	c.Assert(maxCols, Equals, 1)
 
 
@@ -420,7 +515,8 @@ func (l *LibSuite) TestReadRowsFromSheetWithLeadingEmptyCols(c *C) {
 
 
 	file := new(File)
 	file := new(File)
 	file.referenceTable = MakeSharedStringRefTable(sst)
 	file.referenceTable = MakeSharedStringRefTable(sst)
-	rows, cols, maxCols, maxRows := readRowsFromSheet(worksheet, file)
+	sheet := new(Sheet)
+	rows, cols, maxCols, maxRows := readRowsFromSheet(worksheet, file, sheet)
 	c.Assert(maxRows, Equals, 2)
 	c.Assert(maxRows, Equals, 2)
 	c.Assert(maxCols, Equals, 4)
 	c.Assert(maxCols, Equals, 4)
 
 
@@ -526,11 +622,13 @@ func (l *LibSuite) TestReadRowsFromSheetWithEmptyCells(c *C) {
 	c.Assert(err, IsNil)
 	c.Assert(err, IsNil)
 	file := new(File)
 	file := new(File)
 	file.referenceTable = MakeSharedStringRefTable(sst)
 	file.referenceTable = MakeSharedStringRefTable(sst)
-	rows, cols, maxCols, maxRows := readRowsFromSheet(worksheet, file)
+	sheet := new(Sheet)
+	rows, cols, maxCols, maxRows := readRowsFromSheet(worksheet, file, sheet)
 	c.Assert(maxRows, Equals, 3)
 	c.Assert(maxRows, Equals, 3)
 	c.Assert(maxCols, Equals, 3)
 	c.Assert(maxCols, Equals, 3)
 
 
 	row := rows[2]
 	row := rows[2]
+	c.Assert(row.Sheet, Equals, sheet)
 	c.Assert(len(row.Cells), Equals, 3)
 	c.Assert(len(row.Cells), Equals, 3)
 
 
 	cell1 := row.Cells[0]
 	cell1 := row.Cells[0]
@@ -568,11 +666,13 @@ func (l *LibSuite) TestReadRowsFromSheetWithTrailingEmptyCells(c *C) {
 
 
 	file := new(File)
 	file := new(File)
 	file.referenceTable = MakeSharedStringRefTable(sst)
 	file.referenceTable = MakeSharedStringRefTable(sst)
-	rows, _, maxCol, maxRow := readRowsFromSheet(worksheet, file)
+	sheet := new(Sheet)
+	rows, _, maxCol, maxRow := readRowsFromSheet(worksheet, file, sheet)
 	c.Assert(maxCol, Equals, 4)
 	c.Assert(maxCol, Equals, 4)
 	c.Assert(maxRow, Equals, 8)
 	c.Assert(maxRow, Equals, 8)
 
 
 	row = rows[0]
 	row = rows[0]
+	c.Assert(row.Sheet, Equals, sheet)
 	c.Assert(len(row.Cells), Equals, 4)
 	c.Assert(len(row.Cells), Equals, 4)
 
 
 	cell1 = row.Cells[0]
 	cell1 = row.Cells[0]
@@ -588,6 +688,7 @@ func (l *LibSuite) TestReadRowsFromSheetWithTrailingEmptyCells(c *C) {
 	c.Assert(cell4.Value, Equals, "D")
 	c.Assert(cell4.Value, Equals, "D")
 
 
 	row = rows[1]
 	row = rows[1]
+	c.Assert(row.Sheet, Equals, sheet)
 	c.Assert(len(row.Cells), Equals, 4)
 	c.Assert(len(row.Cells), Equals, 4)
 
 
 	cell1 = row.Cells[0]
 	cell1 = row.Cells[0]
@@ -675,10 +776,12 @@ func (l *LibSuite) TestReadRowsFromSheetWithMultipleSpans(c *C) {
 	c.Assert(err, IsNil)
 	c.Assert(err, IsNil)
 	file := new(File)
 	file := new(File)
 	file.referenceTable = MakeSharedStringRefTable(sst)
 	file.referenceTable = MakeSharedStringRefTable(sst)
-	rows, _, maxCols, maxRows := readRowsFromSheet(worksheet, file)
+	sheet := new(Sheet)
+	rows, _, maxCols, maxRows := readRowsFromSheet(worksheet, file, sheet)
 	c.Assert(maxRows, Equals, 2)
 	c.Assert(maxRows, Equals, 2)
 	c.Assert(maxCols, Equals, 4)
 	c.Assert(maxCols, Equals, 4)
 	row := rows[0]
 	row := rows[0]
+	c.Assert(row.Sheet, Equals, sheet)
 	c.Assert(len(row.Cells), Equals, 4)
 	c.Assert(len(row.Cells), Equals, 4)
 	cell1 := row.Cells[0]
 	cell1 := row.Cells[0]
 	c.Assert(cell1.Value, Equals, "Foo")
 	c.Assert(cell1.Value, Equals, "Foo")
@@ -748,10 +851,12 @@ func (l *LibSuite) TestReadRowsFromSheetWithMultipleTypes(c *C) {
 	c.Assert(err, IsNil)
 	c.Assert(err, IsNil)
 	file := new(File)
 	file := new(File)
 	file.referenceTable = MakeSharedStringRefTable(sst)
 	file.referenceTable = MakeSharedStringRefTable(sst)
-	rows, _, maxCols, maxRows := readRowsFromSheet(worksheet, file)
+	sheet := new(Sheet)
+	rows, _, maxCols, maxRows := readRowsFromSheet(worksheet, file, sheet)
 	c.Assert(maxRows, Equals, 1)
 	c.Assert(maxRows, Equals, 1)
 	c.Assert(maxCols, Equals, 6)
 	c.Assert(maxCols, Equals, 6)
 	row := rows[0]
 	row := rows[0]
+	c.Assert(row.Sheet, Equals, sheet)
 	c.Assert(len(row.Cells), Equals, 6)
 	c.Assert(len(row.Cells), Equals, 6)
 
 
 	cell1 := row.Cells[0]
 	cell1 := row.Cells[0]
@@ -815,10 +920,12 @@ func (l *LibSuite) TestReadRowsFromSheetWithHiddenColumn(c *C) {
 	c.Assert(err, IsNil)
 	c.Assert(err, IsNil)
 	file := new(File)
 	file := new(File)
 	file.referenceTable = MakeSharedStringRefTable(sst)
 	file.referenceTable = MakeSharedStringRefTable(sst)
-	rows, _, maxCols, maxRows := readRowsFromSheet(worksheet, file)
+	sheet := new(Sheet)
+	rows, _, maxCols, maxRows := readRowsFromSheet(worksheet, file, sheet)
 	c.Assert(maxRows, Equals, 1)
 	c.Assert(maxRows, Equals, 1)
 	c.Assert(maxCols, Equals, 2)
 	c.Assert(maxCols, Equals, 2)
 	row := rows[0]
 	row := rows[0]
+	c.Assert(row.Sheet, Equals, sheet)
 	c.Assert(len(row.Cells), Equals, 2)
 	c.Assert(len(row.Cells), Equals, 2)
 
 
 	cell1 := row.Cells[0]
 	cell1 := row.Cells[0]
@@ -842,9 +949,11 @@ func (l *LibSuite) TestReadRowFromRaw(c *C) {
 	cell = xlsxC{R: "A1"}
 	cell = xlsxC{R: "A1"}
 	cell = xlsxC{R: "A2"}
 	cell = xlsxC{R: "A2"}
 	rawRow.C = append(rawRow.C, cell)
 	rawRow.C = append(rawRow.C, cell)
-	row = makeRowFromRaw(rawRow)
+	sheet := new(Sheet)
+	row = makeRowFromRaw(rawRow, sheet)
 	c.Assert(row, NotNil)
 	c.Assert(row, NotNil)
 	c.Assert(row.Cells, HasLen, 1)
 	c.Assert(row.Cells, HasLen, 1)
+	c.Assert(row.Sheet, Equals, sheet)
 }
 }
 
 
 // When a cell claims it is at a position greater than its ordinal
 // When a cell claims it is at a position greater than its ordinal
@@ -859,9 +968,11 @@ func (l *LibSuite) TestReadRowFromRawWithMissingCells(c *C) {
 	rawRow.C = append(rawRow.C, cell)
 	rawRow.C = append(rawRow.C, cell)
 	cell = xlsxC{R: "E1"}
 	cell = xlsxC{R: "E1"}
 	rawRow.C = append(rawRow.C, cell)
 	rawRow.C = append(rawRow.C, cell)
-	row = makeRowFromRaw(rawRow)
+	sheet := new(Sheet)
+	row = makeRowFromRaw(rawRow, sheet)
 	c.Assert(row, NotNil)
 	c.Assert(row, NotNil)
 	c.Assert(row.Cells, HasLen, 5)
 	c.Assert(row.Cells, HasLen, 5)
+	c.Assert(row.Sheet, Equals, sheet)
 }
 }
 
 
 // We can cope with missing coordinate references
 // We can cope with missing coordinate references
@@ -879,9 +990,11 @@ func (l *LibSuite) TestReadRowFromRawWithPartialCoordinates(c *C) {
 	rawRow.C = append(rawRow.C, cell)
 	rawRow.C = append(rawRow.C, cell)
 	cell = xlsxC{}
 	cell = xlsxC{}
 	rawRow.C = append(rawRow.C, cell)
 	rawRow.C = append(rawRow.C, cell)
-	row = makeRowFromRaw(rawRow)
+	sheet := new(Sheet)
+	row = makeRowFromRaw(rawRow, sheet)
 	c.Assert(row, NotNil)
 	c.Assert(row, NotNil)
 	c.Assert(row.Cells, HasLen, 27)
 	c.Assert(row.Cells, HasLen, 27)
+	c.Assert(row.Sheet, Equals, sheet)
 }
 }
 
 
 func (l *LibSuite) TestSharedFormulas(c *C) {
 func (l *LibSuite) TestSharedFormulas(c *C) {
@@ -935,7 +1048,8 @@ func (l *LibSuite) TestSharedFormulas(c *C) {
 	c.Assert(err, IsNil)
 	c.Assert(err, IsNil)
 
 
 	file := new(File)
 	file := new(File)
-	rows, _, maxCols, maxRows := readRowsFromSheet(worksheet, file)
+	sheet := new(Sheet)
+	rows, _, maxCols, maxRows := readRowsFromSheet(worksheet, file, sheet)
 	c.Assert(maxCols, Equals, 3)
 	c.Assert(maxCols, Equals, 3)
 	c.Assert(maxRows, Equals, 2)
 	c.Assert(maxRows, Equals, 2)
 
 

+ 16 - 8
sheet.go

@@ -54,8 +54,7 @@ func (s *Sheet) maybeAddCol(cellCount int) {
 			Min:       cellCount,
 			Min:       cellCount,
 			Max:       cellCount,
 			Max:       cellCount,
 			Hidden:    false,
 			Hidden:    false,
-			Collapsed: false,
-			Width:     ColWidth}
+			Collapsed: false}
 		s.Cols = append(s.Cols, col)
 		s.Cols = append(s.Cols, col)
 		s.MaxCol = cellCount
 		s.MaxCol = cellCount
 	}
 	}
@@ -110,6 +109,11 @@ func (s *Sheet) makeXLSXSheet(refTable *RefTable, styles *xlsxStyleSheet) *xlsxW
 	maxRow := 0
 	maxRow := 0
 	maxCell := 0
 	maxCell := 0
 
 
+	if s.SheetFormat.DefaultRowHeight != 0 {
+		worksheet.SheetFormatPr.DefaultRowHeight = s.SheetFormat.DefaultRowHeight
+	}
+	worksheet.SheetFormatPr.DefaultColWidth = s.SheetFormat.DefaultColWidth
+
 	colsXfIdList := make([]int, len(s.Cols))
 	colsXfIdList := make([]int, len(s.Cols))
 	worksheet.Cols = xlsxCols{Col: []xlsxCol{}}
 	worksheet.Cols = xlsxCols{Col: []xlsxCol{}}
 	for c, col := range s.Cols {
 	for c, col := range s.Cols {
@@ -123,16 +127,20 @@ func (s *Sheet) makeXLSXSheet(refTable *RefTable, styles *xlsxStyleSheet) *xlsxW
 		}
 		}
 		colsXfIdList[c] = XfId
 		colsXfIdList[c] = XfId
 
 
+		var customWidth int
 		if col.Width == 0 {
 		if col.Width == 0 {
 			col.Width = ColWidth
 			col.Width = ColWidth
+		} else {
+			customWidth = 1
 		}
 		}
 		worksheet.Cols.Col = append(worksheet.Cols.Col,
 		worksheet.Cols.Col = append(worksheet.Cols.Col,
 			xlsxCol{Min: col.Min,
 			xlsxCol{Min: col.Min,
-				Max:       col.Max,
-				Hidden:    col.Hidden,
-				Width:     col.Width,
-				Collapsed: col.Collapsed,
-				Style:     XfId,
+				Max:         col.Max,
+				Hidden:      col.Hidden,
+				Width:       col.Width,
+				CustomWidth: customWidth,
+				Collapsed:   col.Collapsed,
+				Style:       XfId,
 			})
 			})
 	}
 	}
 
 
@@ -233,7 +241,7 @@ func handleStyleForXLSX(style *Style, NumFmtId int, styles *xlsxStyleSheet) (XfI
 
 
 	// HACK - adding light grey fill, as in OO and Google
 	// HACK - adding light grey fill, as in OO and Google
 	greyfill := xlsxFill{}
 	greyfill := xlsxFill{}
-	greyfill.PatternFill.PatternType = "lightGrey"
+	greyfill.PatternFill.PatternType = "lightGray"
 	styles.addFill(greyfill)
 	styles.addFill(greyfill)
 
 
 	borderId := styles.addBorder(xBorder)
 	borderId := styles.addBorder(xBorder)

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 28 - 1
sheet_test.go


+ 40 - 15
style.go

@@ -18,9 +18,9 @@ type Style struct {
 // Return a new Style structure initialised with the default values.
 // Return a new Style structure initialised with the default values.
 func NewStyle() *Style {
 func NewStyle() *Style {
 	return &Style{
 	return &Style{
-		Font:   *DefaulFont(),
-		Border: *DefaulBorder(),
-		Fill:   *DefaulFill(),
+		Font:   *DefaultFont(),
+		Border: *DefaultBorder(),
+		Fill:   *DefaultFill(),
 	}
 	}
 }
 }
 
 
@@ -56,10 +56,22 @@ func (style *Style) makeXLSXStyleElements() (xFont xlsxFont, xFill xlsxFill, xBo
 	xPatternFill.FgColor.RGB = style.Fill.FgColor
 	xPatternFill.FgColor.RGB = style.Fill.FgColor
 	xPatternFill.BgColor.RGB = style.Fill.BgColor
 	xPatternFill.BgColor.RGB = style.Fill.BgColor
 	xFill.PatternFill = xPatternFill
 	xFill.PatternFill = xPatternFill
-	xBorder.Left = xlsxLine{Style: style.Border.Left}
-	xBorder.Right = xlsxLine{Style: style.Border.Right}
-	xBorder.Top = xlsxLine{Style: style.Border.Top}
-	xBorder.Bottom = xlsxLine{Style: style.Border.Bottom}
+	xBorder.Left = xlsxLine{
+		Style: style.Border.Left,
+		Color: xlsxColor{RGB: style.Border.LeftColor},
+	}
+	xBorder.Right = xlsxLine{
+		Style: style.Border.Right,
+		Color: xlsxColor{RGB: style.Border.RightColor},
+	}
+	xBorder.Top = xlsxLine{
+		Style: style.Border.Top,
+		Color: xlsxColor{RGB: style.Border.TopColor},
+	}
+	xBorder.Bottom = xlsxLine{
+		Style: style.Border.Bottom,
+		Color: xlsxColor{RGB: style.Border.BottomColor},
+	}
 	xCellXf = makeXLSXCellElement()
 	xCellXf = makeXLSXCellElement()
 	xCellXf.ApplyBorder = style.ApplyBorder
 	xCellXf.ApplyBorder = style.ApplyBorder
 	xCellXf.ApplyFill = style.ApplyFill
 	xCellXf.ApplyFill = style.ApplyFill
@@ -83,14 +95,27 @@ func makeXLSXCellElement() (xCellXf xlsxXf) {
 // Border is a high level structure intended to provide user access to
 // Border is a high level structure intended to provide user access to
 // the contents of Border Style within an Sheet.
 // the contents of Border Style within an Sheet.
 type Border struct {
 type Border struct {
-	Left   string
-	Right  string
-	Top    string
-	Bottom string
+	Left        string
+	LeftColor   string
+	Right       string
+	RightColor  string
+	Top         string
+	TopColor    string
+	Bottom      string
+	BottomColor string
 }
 }
 
 
 func NewBorder(left, right, top, bottom string) *Border {
 func NewBorder(left, right, top, bottom string) *Border {
-	return &Border{Left: left, Right: right, Top: top, Bottom: bottom}
+	return &Border{
+		Left:        left,
+		LeftColor:   "",
+		Right:       right,
+		RightColor:  "",
+		Top:         top,
+		TopColor:    "",
+		Bottom:      bottom,
+		BottomColor: "",
+	}
 }
 }
 
 
 // Fill is a high level structure intended to provide user access to
 // Fill is a high level structure intended to provide user access to
@@ -138,15 +163,15 @@ func SetDefaultFont(size int, name string) {
 	defaultFontName = name
 	defaultFontName = name
 }
 }
 
 
-func DefaulFont() *Font {
+func DefaultFont() *Font {
 	return NewFont(defaultFontSize, defaultFontName)
 	return NewFont(defaultFontSize, defaultFontName)
 }
 }
 
 
-func DefaulFill() *Fill {
+func DefaultFill() *Fill {
 	return NewFill("none", "FFFFFFFF", "00000000")
 	return NewFill("none", "FFFFFFFF", "00000000")
 
 
 }
 }
 
 
-func DefaulBorder() *Border {
+func DefaultBorder() *Border {
 	return NewBorder("none", "none", "none", "none")
 	return NewBorder("none", "none", "none", "none")
 }
 }

+ 4 - 4
style_test.go

@@ -13,11 +13,11 @@ func (s *StyleSuite) TestNewStyle(c *C) {
 	c.Assert(style, NotNil)
 	c.Assert(style, NotNil)
 }
 }
 
 
-func (s *StyleSuite) TestNewStyleDefaults(c *C) {
+func (s *StyleSuite) TestNewStyleDefaultts(c *C) {
 	style := NewStyle()
 	style := NewStyle()
-	c.Assert(style.Font, Equals, *DefaulFont())
-	c.Assert(style.Fill, Equals, *DefaulFill())
-	c.Assert(style.Border, Equals, *DefaulBorder())
+	c.Assert(style.Font, Equals, *DefaultFont())
+	c.Assert(style.Fill, Equals, *DefaultFill())
+	c.Assert(style.Border, Equals, *DefaultBorder())
 }
 }
 
 
 func (s *StyleSuite) TestMakeXLSXStyleElements(c *C) {
 func (s *StyleSuite) TestMakeXLSXStyleElements(c *C) {

BIN
testdocs/badfile_noWorkbookRels.xlsx


BIN
testdocs/badfile_noWorksheets.xlsx


+ 31 - 10
xmlStyle.go

@@ -59,10 +59,10 @@ var builtInNumFmt = map[int]string{
 
 
 const (
 const (
 	builtInNumFmtIndex_GENERAL = int(0)
 	builtInNumFmtIndex_GENERAL = int(0)
-	builtInNumFmtIndex_INT = int(1)
-	builtInNumFmtIndex_FLOAT = int(2)
-	builtInNumFmtIndex_DATE = int(14)
-	builtInNumFmtIndex_STRING = int(49)
+	builtInNumFmtIndex_INT     = int(1)
+	builtInNumFmtIndex_FLOAT   = int(2)
+	builtInNumFmtIndex_DATE    = int(14)
+	builtInNumFmtIndex_STRING  = int(49)
 )
 )
 
 
 // xlsxStyle directly maps the styleSheet element in the namespace
 // xlsxStyle directly maps the styleSheet element in the namespace
@@ -137,9 +137,13 @@ func (styles *xlsxStyleSheet) getStyle(styleIndex int) (style *Style) {
 			var border xlsxBorder
 			var border xlsxBorder
 			border = styles.Borders.Border[xf.BorderId]
 			border = styles.Borders.Border[xf.BorderId]
 			style.Border.Left = border.Left.Style
 			style.Border.Left = border.Left.Style
+			style.Border.LeftColor = border.Left.Color.RGB
 			style.Border.Right = border.Right.Style
 			style.Border.Right = border.Right.Style
+			style.Border.RightColor = border.Right.Color.RGB
 			style.Border.Top = border.Top.Style
 			style.Border.Top = border.Top.Style
+			style.Border.TopColor = border.Top.Color.RGB
 			style.Border.Bottom = border.Bottom.Style
 			style.Border.Bottom = border.Bottom.Style
+			style.Border.BottomColor = border.Bottom.Color.RGB
 		}
 		}
 
 
 		if xf.FillId > -1 && xf.FillId < styles.Fills.Count {
 		if xf.FillId > -1 && xf.FillId < styles.Fills.Count {
@@ -698,19 +702,35 @@ func (border *xlsxBorder) Marshal() (result string, err error) {
 	subparts := ""
 	subparts := ""
 	if border.Left.Style != "" {
 	if border.Left.Style != "" {
 		emit = true
 		emit = true
-		subparts += fmt.Sprintf(`<left style="%s"/>`, border.Left.Style)
+		subparts += fmt.Sprintf(`<left style="%s">`, border.Left.Style)
+		if border.Left.Color.RGB != "" {
+			subparts += fmt.Sprintf(`<color rgb="%s"/>`, border.Left.Color.RGB)
+		}
+		subparts += `</left>`
 	}
 	}
 	if border.Right.Style != "" {
 	if border.Right.Style != "" {
 		emit = true
 		emit = true
-		subparts += fmt.Sprintf(`<right style="%s"/>`, border.Right.Style)
+		subparts += fmt.Sprintf(`<right style="%s">`, border.Right.Style)
+		if border.Right.Color.RGB != "" {
+			subparts += fmt.Sprintf(`<color rgb="%s"/>`, border.Right.Color.RGB)
+		}
+		subparts += `</right>`
 	}
 	}
 	if border.Top.Style != "" {
 	if border.Top.Style != "" {
 		emit = true
 		emit = true
-		subparts += fmt.Sprintf(`<top style="%s"/>`, border.Top.Style)
+		subparts += fmt.Sprintf(`<top style="%s">`, border.Top.Style)
+		if border.Top.Color.RGB != "" {
+			subparts += fmt.Sprintf(`<color rgb="%s"/>`, border.Top.Color.RGB)
+		}
+		subparts += `</top>`
 	}
 	}
 	if border.Bottom.Style != "" {
 	if border.Bottom.Style != "" {
 		emit = true
 		emit = true
-		subparts += fmt.Sprintf(`<bottom style="%s"/>`, border.Bottom.Style)
+		subparts += fmt.Sprintf(`<bottom style="%s">`, border.Bottom.Style)
+		if border.Bottom.Color.RGB != "" {
+			subparts += fmt.Sprintf(`<color rgb="%s"/>`, border.Bottom.Color.RGB)
+		}
+		subparts += `</bottom>`
 	}
 	}
 	if emit {
 	if emit {
 		result += `<border>`
 		result += `<border>`
@@ -725,11 +745,12 @@ func (border *xlsxBorder) Marshal() (result string, err error) {
 // 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 xlsxLine struct {
 type xlsxLine struct {
-	Style string `xml:"style,attr,omitempty"`
+	Style string    `xml:"style,attr,omitempty"`
+	Color xlsxColor `xml:"color,omitempty"`
 }
 }
 
 
 func (line *xlsxLine) Equals(other xlsxLine) bool {
 func (line *xlsxLine) Equals(other xlsxLine) bool {
-	return line.Style == other.Style
+	return line.Style == other.Style && line.Color.Equals(other.Color)
 }
 }
 
 
 // xlsxCellStyleXfs directly maps the cellStyleXfs element in the
 // xlsxCellStyleXfs directly maps the cellStyleXfs element in the

+ 1 - 1
xmlStyle_test.go

@@ -71,7 +71,7 @@ func (x *XMLStyleSuite) TestMarshalXlsxStyleSheetWithABorder(c *C) {
 	styles.Borders.Border[0] = border
 	styles.Borders.Border[0] = border
 
 
 	expected := `<?xml version="1.0" encoding="UTF-8"?>
 	expected := `<?xml version="1.0" encoding="UTF-8"?>
-<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><borders count="1"><border><left style="solid"/><top style="none"/></border></borders></styleSheet>`
+<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><borders count="1"><border><left style="solid"></left><top style="none"></top></border></borders></styleSheet>`
 	result, err := styles.Marshal()
 	result, err := styles.Marshal()
 	c.Assert(err, IsNil)
 	c.Assert(err, IsNil)
 	c.Assert(string(result), Equals, expected)
 	c.Assert(string(result), Equals, expected)

+ 7 - 6
xmlWorksheet.go

@@ -192,12 +192,13 @@ type xlsxCols 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 xlsxCol struct {
 type xlsxCol struct {
-	Collapsed bool    `xml:"collapsed,attr"`
-	Hidden    bool    `xml:"hidden,attr"`
-	Max       int     `xml:"max,attr"`
-	Min       int     `xml:"min,attr"`
-	Style     int     `xml:"style,attr"`
-	Width     float64 `xml:"width,attr"`
+	Collapsed   bool    `xml:"collapsed,attr"`
+	Hidden      bool    `xml:"hidden,attr"`
+	Max         int     `xml:"max,attr"`
+	Min         int     `xml:"min,attr"`
+	Style       int     `xml:"style,attr"`
+	Width       float64 `xml:"width,attr"`
+	CustomWidth int     `xml:"customWidth,attr,omitempty"`
 }
 }
 
 
 // xlsxDimension directly maps the dimension element in the namespace
 // xlsxDimension directly maps the dimension element in the namespace

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.