Przeglądaj źródła

Merge branch 'master' into master

Geoffrey J. Teale 8 lat temu
rodzic
commit
0692428a3e
11 zmienionych plików z 115 dodań i 25 usunięć
  1. 2 0
      .travis.yml
  2. 18 3
      cell.go
  3. 30 9
      cell_test.go
  4. 3 3
      file.go
  5. 18 1
      lib.go
  6. 23 0
      lib_test.go
  7. 5 0
      row.go
  8. 1 1
      sheet.go
  9. BIN
      testdocs/inlineStrings.xlsx
  10. 9 3
      xmlStyle.go
  11. 6 5
      xmlWorksheet.go

+ 2 - 0
.travis.yml

@@ -6,6 +6,8 @@ install:
 go:
   - 1.5
   - 1.6
+  - 1.7
+  - 1.8
   - tip
 
 script:

+ 18 - 3
cell.go

@@ -391,8 +391,6 @@ func parseTime(c *Cell) (string, error) {
 		{"mmm", "Jan"},
 		{"mmss", "0405"},
 		{"ss", "05"},
-		{"hh", "15"},
-		{"h", "3"},
 		{"mm:", "04:"},
 		{":mm", ":04"},
 		{"mm", "01"},
@@ -401,6 +399,16 @@ func parseTime(c *Cell) (string, error) {
 		{"%%%%", "January"},
 		{"&&&&", "Monday"},
 	}
+	// It is the presence of the "am/pm" indicator that determins
+	// if this is a 12 hour or 24 hours time format, not the
+	// number of 'h' characters.
+	if is12HourTime(format) {
+		format = strings.Replace(format, "hh", "03", 1)
+		format = strings.Replace(format, "h", "3", 1)
+	} else {
+		format = strings.Replace(format, "hh", "15", 1)
+		format = strings.Replace(format, "h", "15", 1)
+	}
 	for _, repl := range replacements {
 		format = strings.Replace(format, repl.xltime, repl.gotime, 1)
 	}
@@ -408,6 +416,7 @@ func parseTime(c *Cell) (string, error) {
 	// possible dangling colon that would remain.
 	if val.Hour() < 1 {
 		format = strings.Replace(format, "]:", "]", 1)
+		format = strings.Replace(format, "[03]", "", 1)
 		format = strings.Replace(format, "[3]", "", 1)
 		format = strings.Replace(format, "[15]", "", 1)
 	} else {
@@ -421,7 +430,7 @@ func parseTime(c *Cell) (string, error) {
 // a time.Time.
 func isTimeFormat(format string) bool {
 	dateParts := []string{
-		"yy", "hh", "am", "pm", "ss", "mm", ":",
+		"yy", "hh", "h", "am/pm", "AM/PM", "A/P", "a/p", "ss", "mm", ":",
 	}
 	for _, part := range dateParts {
 		if strings.Contains(format, part) {
@@ -430,3 +439,9 @@ func isTimeFormat(format string) bool {
 	}
 	return false
 }
+
+// is12HourTime checks whether an Excel time format string is a 12
+// hours form.
+func is12HourTime(format string) bool {
+	return strings.Contains(format, "am/pm") || strings.Contains(format, "AM/PM") || strings.Contains(format, "a/p") || strings.Contains(format, "A/P")
+}

+ 30 - 9
cell_test.go

@@ -259,34 +259,34 @@ func (l *CellSuite) TestFormattedValue(c *C) {
 	fvc.Equals(smallCell, "12:10:04 am")
 
 	cell.NumFmt = "h:mm"
-	fvc.Equals(cell, "6:00")
+	fvc.Equals(cell, "18:00")
 	smallCell.NumFmt = "h:mm"
-	fvc.Equals(smallCell, "12:10")
+	fvc.Equals(smallCell, "00:10")
 	smallCell.NumFmt = "hh:mm"
 	fvc.Equals(smallCell, "00:10")
 
 	cell.NumFmt = "h:mm:ss"
-	fvc.Equals(cell, "6:00:00")
+	fvc.Equals(cell, "18:00:00")
 	cell.NumFmt = "hh:mm:ss"
 	fvc.Equals(cell, "18:00:00")
 
 	smallCell.NumFmt = "hh:mm:ss"
 	fvc.Equals(smallCell, "00:10:04")
 	smallCell.NumFmt = "h:mm:ss"
-	fvc.Equals(smallCell, "12:10:04")
+	fvc.Equals(smallCell, "00:10:04")
 
 	cell.NumFmt = "m/d/yy h:mm"
-	fvc.Equals(cell, "11/22/03 6:00")
+	fvc.Equals(cell, "11/22/03 18:00")
 	cell.NumFmt = "m/d/yy hh:mm"
 	fvc.Equals(cell, "11/22/03 18:00")
 	smallCell.NumFmt = "m/d/yy h:mm"
-	fvc.Equals(smallCell, "12/30/99 12:10")
+	fvc.Equals(smallCell, "12/30/99 00:10")
 	smallCell.NumFmt = "m/d/yy hh:mm"
 	fvc.Equals(smallCell, "12/30/99 00:10")
 	earlyCell.NumFmt = "m/d/yy hh:mm"
 	fvc.Equals(earlyCell, "1/1/00 02:24")
 	earlyCell.NumFmt = "m/d/yy h:mm"
-	fvc.Equals(earlyCell, "1/1/00 2:24")
+	fvc.Equals(earlyCell, "1/1/00 02:24")
 
 	cell.NumFmt = "mm:ss"
 	fvc.Equals(cell, "00:00")
@@ -296,7 +296,7 @@ func (l *CellSuite) TestFormattedValue(c *C) {
 	cell.NumFmt = "[hh]:mm:ss"
 	fvc.Equals(cell, "18:00:00")
 	cell.NumFmt = "[h]:mm:ss"
-	fvc.Equals(cell, "6:00:00")
+	fvc.Equals(cell, "18:00:00")
 	smallCell.NumFmt = "[h]:mm:ss"
 	fvc.Equals(smallCell, "10:04")
 
@@ -362,7 +362,7 @@ func (l *CellSuite) TestFormattedValue(c *C) {
 	fvc.Equals(cell, "22/11/2003")
 
 	cell.NumFmt = "mm/dd/yy hh:mm am/pm"
-	fvc.Equals(cell, "11/22/03 18:00 pm")
+	fvc.Equals(cell, "11/22/03 06:00 pm")
 	cell.NumFmt = "mm/dd/yy h:mm am/pm"
 	fvc.Equals(cell, "11/22/03 6:00 pm")
 
@@ -534,5 +534,26 @@ func (s *CellSuite) TestSetDateWithOptions(c *C) {
 	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)
+	c.Assert(isTimeFormat("h"), Equals, true)
+	c.Assert(isTimeFormat("am/pm"), Equals, true)
+	c.Assert(isTimeFormat("AM/PM"), Equals, true)
+	c.Assert(isTimeFormat("A/P"), Equals, true)
+	c.Assert(isTimeFormat("a/p"), Equals, true)
+	c.Assert(isTimeFormat("ss"), Equals, true)
+	c.Assert(isTimeFormat("mm"), Equals, true)
+	c.Assert(isTimeFormat(":"), Equals, true)
+	c.Assert(isTimeFormat("z"), Equals, false)
+}
 
+func (s *CellSuite) TestIs12HourtTime(c *C) {
+	c.Assert(is12HourTime("am/pm"), Equals, true)
+	c.Assert(is12HourTime("AM/PM"), Equals, true)
+	c.Assert(is12HourTime("a/p"), Equals, true)
+	c.Assert(is12HourTime("A/P"), Equals, true)
+	c.Assert(is12HourTime("x"), Equals, false)
 }

+ 3 - 3
file.go

@@ -4,12 +4,12 @@ import (
 	"archive/zip"
 	"bytes"
 	"encoding/xml"
+	"errors"
 	"fmt"
 	"io"
 	"os"
 	"strconv"
 	"strings"
-	"errors"
 )
 
 // File is a high level structure providing a slice of Sheet structs
@@ -219,8 +219,8 @@ func (f *File) MarshallParts() (map[string]string, error) {
 		f.styles = newXlsxStyleSheet(f.theme)
 	}
 	f.styles.reset()
-	if len(f.Sheets)==0 {
-		err:= errors.New("Workbook must contains atleast one worksheet")
+	if len(f.Sheets) == 0 {
+		err := errors.New("Workbook must contains atleast one worksheet")
 		return nil, err
 	}
 	for _, sheet := range f.Sheets {

+ 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) {

+ 5 - 0
row.go

@@ -9,6 +9,11 @@ type Row struct {
 	isCustom     bool
 }
 
+func (r *Row) SetHeight(ht float64) {
+	r.Height = ht
+	r.isCustom = true
+}
+
 func (r *Row) SetHeightCM(ht float64) {
 	r.Height = ht * 28.3464567 // Convert CM to postscript points
 	r.isCustom = true

+ 1 - 1
sheet.go

@@ -237,7 +237,7 @@ func (s *Sheet) makeXLSXSheet(refTable *RefTable, styles *xlsxStyleSheet) *xlsxW
 		if col.Width == 0 {
 			col.Width = ColWidth
 			customWidth = false
-			
+
 		} else {
 			customWidth = true
 		}

BIN
testdocs/inlineStrings.xlsx


+ 9 - 3
xmlStyle.go

@@ -8,6 +8,7 @@
 package xlsx
 
 import (
+	"bytes"
 	"encoding/xml"
 	"fmt"
 	"strconv"
@@ -57,9 +58,9 @@ var builtInNumFmt = map[int]string{
 	49: "@",
 }
 
-var builtInNumFmtInv = make(map[string]int,40)
+var builtInNumFmtInv = make(map[string]int, 40)
 
-func init () {
+func init() {
 	for k, v := range builtInNumFmt {
 		builtInNumFmtInv[v] = k
 	}
@@ -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