Просмотр исходного кода

Merge remote-tracking branch 'upstream/master'

Juan Carlos García Martínez 8 лет назад
Родитель
Сommit
5012e7117b
7 измененных файлов с 121 добавлено и 9 удалено
  1. 32 2
      cell.go
  2. 35 0
      cell_test.go
  3. 18 1
      lib.go
  4. 23 0
      lib_test.go
  5. BIN
      testdocs/inlineStrings.xlsx
  6. 7 1
      xmlStyle.go
  7. 6 5
      xmlWorksheet.go

+ 32 - 2
cell.go

@@ -122,13 +122,43 @@ func TimeToExcelTime(t time.Time) float64 {
 	return float64(t.UnixNano())/8.64e13 + 25569.0
 }
 
+// DateTimeOptions are additional options for exporting times
+type DateTimeOptions struct {
+	// Location allows calculating times in other timezones/locations
+	Location *time.Location
+	// ExcelTimeFormat is the string you want excel to use to format the datetime
+	ExcelTimeFormat string
+}
+
+var (
+	DefaultDateFormat     = builtInNumFmt[14]
+	DefaultDateTimeFormat = builtInNumFmt[22]
+
+	DefaultDateOptions = DateTimeOptions{
+		Location:        timeLocationUTC,
+		ExcelTimeFormat: DefaultDateFormat,
+	}
+
+	DefaultDateTimeOptions = DateTimeOptions{
+		Location:        timeLocationUTC,
+		ExcelTimeFormat: DefaultDateTimeFormat,
+	}
+)
+
 // SetDate sets the value of a cell to a float.
 func (c *Cell) SetDate(t time.Time) {
-	c.SetDateTimeWithFormat(float64(int64(TimeToExcelTime(TimeToUTCTime(t)))), builtInNumFmt[14])
+	c.SetDateWithOptions(t, DefaultDateOptions)
 }
 
 func (c *Cell) SetDateTime(t time.Time) {
-	c.SetDateTimeWithFormat(TimeToExcelTime(TimeToUTCTime(t)), builtInNumFmt[22])
+	c.SetDateWithOptions(t, DefaultDateTimeOptions)
+}
+
+// SetDateWithOptions allows for more granular control when exporting dates and times
+func (c *Cell) SetDateWithOptions(t time.Time, options DateTimeOptions) {
+	_, offset := t.In(options.Location).Zone()
+	t = time.Unix(t.Unix()+int64(offset), 0)
+	c.SetDateTimeWithFormat(TimeToExcelTime(t.In(timeLocationUTC)), options.ExcelTimeFormat)
 }
 
 func (c *Cell) SetDateTimeWithFormat(n float64, format string) {

+ 35 - 0
cell_test.go

@@ -501,6 +501,41 @@ func (s *CellSuite) TestSetValue(c *C) {
 	c.Assert(cell.Value, Equals, "[test]")
 }
 
+func (s *CellSuite) TestSetDateWithOptions(c *C) {
+	cell := Cell{}
+
+	// time
+	cell.SetDate(time.Unix(0, 0))
+	val, err := cell.Float()
+	c.Assert(err, IsNil)
+	c.Assert(math.Floor(val), Equals, 25569.0)
+
+	// our test subject
+	date2016UTC := time.Date(2016, 1, 1, 12, 0, 0, 0, time.UTC)
+
+	// test ny timezone
+	nyTZ, err := time.LoadLocation("America/New_York")
+	c.Assert(err, IsNil)
+	cell.SetDateWithOptions(date2016UTC, DateTimeOptions{
+		ExcelTimeFormat: "test_format1",
+		Location:        nyTZ,
+	})
+	val, err = cell.Float()
+	c.Assert(err, IsNil)
+	c.Assert(val, Equals, TimeToExcelTime(time.Date(2016, 1, 1, 7, 0, 0, 0, time.UTC)))
+
+	// test jp timezone
+	jpTZ, err := time.LoadLocation("Asia/Tokyo")
+	c.Assert(err, IsNil)
+	cell.SetDateWithOptions(date2016UTC, DateTimeOptions{
+		ExcelTimeFormat: "test_format2",
+		Location:        jpTZ,
+	})
+	val, err = cell.Float()
+	c.Assert(err, IsNil)
+	c.Assert(val, Equals, TimeToExcelTime(time.Date(2016, 1, 1, 21, 0, 0, 0, time.UTC)))
+}
+
 func (s *CellSuite) TestIsTimeFormat(c *C) {
 	c.Assert(isTimeFormat("yy"), Equals, true)
 	c.Assert(isTimeFormat("hh"), Equals, true)

+ 18 - 1
lib.go

@@ -442,7 +442,7 @@ func shiftCell(cellID string, dx, dy int) string {
 // 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, sharedFormulas map[int]sharedFormula, cell *Cell) {
-	var data string = rawcell.V
+	var data = rawcell.V
 	if len(data) > 0 {
 		vval := strings.Trim(data, " \t\n\r")
 		switch rawcell.T {
@@ -472,6 +472,23 @@ func fillCellData(rawcell xlsxC, reftable *RefTable, sharedFormulas map[int]shar
 				cell.cellType = CellTypeFormula
 			}
 		}
+	} else {
+		if rawcell.Is != nil {
+			fillCellDataFromInlineString(rawcell, cell)
+		}
+	}
+}
+
+// fillCellDataFromInlineString attempts to get inline string data and put it into a Cell.
+func fillCellDataFromInlineString(rawcell xlsxC, cell *Cell) {
+	if rawcell.Is.T != "" {
+		cell.Value = strings.Trim(rawcell.Is.T, " \t\n\r")
+		cell.cellType = CellTypeInline
+	} else {
+		cell.Value = ""
+		for _, r := range rawcell.Is.R {
+			cell.Value += r.T
+		}
 	}
 }
 

+ 23 - 0
lib_test.go

@@ -28,6 +28,29 @@ func (l *LibSuite) TestReadZipReaderWithFileWithNoWorksheets(c *C) {
 	c.Assert(err.Error(), Equals, "Input xlsx contains no worksheets.")
 }
 
+// Attempt to read data from a file with inlined string sheet data.
+func (l *LibSuite) TestReadWithInlineStrings(c *C) {
+	var xlsxFile *File
+	var err error
+
+	xlsxFile, err = OpenFile("./testdocs/inlineStrings.xlsx")
+	c.Assert(err, IsNil)
+	sheet := xlsxFile.Sheets[0]
+	r1 := sheet.Rows[0]
+	c1 := r1.Cells[1]
+
+	val, err := c1.String()
+	if err != nil {
+		c.Error(err)
+		return
+	}
+	if val == "" {
+		c.Error("Expected a string value")
+		return
+	}
+	c.Assert(val, Equals, "HL Retail - North America - Activity by Day - MTD")
+}
+
 // which they are contained from the XLSX file, even when the
 // worksheet files have arbitrary, non-numeric names.
 func (l *LibSuite) TestReadWorkbookRelationsFromZipFileWithFunnyNames(c *C) {

BIN
testdocs/inlineStrings.xlsx


+ 7 - 1
xmlStyle.go

@@ -8,6 +8,7 @@
 package xlsx
 
 import (
+	"bytes"
 	"encoding/xml"
 	"fmt"
 	"strconv"
@@ -451,7 +452,12 @@ type xlsxNumFmt struct {
 }
 
 func (numFmt *xlsxNumFmt) Marshal() (result string, err error) {
-	return fmt.Sprintf(`<numFmt numFmtId="%d" formatCode="%s"/>`, numFmt.NumFmtId, numFmt.FormatCode), nil
+	formatCode := &bytes.Buffer{}
+	if err := xml.EscapeText(formatCode, []byte(numFmt.FormatCode)); err != nil {
+		return "", err
+	}
+
+	return fmt.Sprintf(`<numFmt numFmtId="%d" formatCode="%s"/>`, numFmt.NumFmtId, formatCode), nil
 }
 
 // xlsxFonts directly maps the fonts element in the namespace

+ 6 - 5
xmlWorksheet.go

@@ -279,11 +279,12 @@ func (mc *xlsxMergeCells) getExtent(cellRef string) (int, int, error) {
 // currently I have not checked it for completeness - it does as much
 // as I need.
 type xlsxC struct {
-	R string `xml:"r,attr"`           // Cell ID, e.g. A1
-	S int    `xml:"s,attr,omitempty"` // Style reference.
-	T string `xml:"t,attr,omitempty"` // Type.
-	F *xlsxF `xml:"f,omitempty"`      // Formula
-	V string `xml:"v,omitempty"`      // Value
+	R  string  `xml:"r,attr"`           // Cell ID, e.g. A1
+	S  int     `xml:"s,attr,omitempty"` // Style reference.
+	T  string  `xml:"t,attr,omitempty"` // Type.
+	F  *xlsxF  `xml:"f,omitempty"`      // Formula
+	V  string  `xml:"v,omitempty"`      // Value
+	Is *xlsxSI `xml:"is,omitempty"`     // Inline String.
 }
 
 // xlsxF directly maps the f element in the namespace