|
|
@@ -11,6 +11,7 @@ package excelize
|
|
|
|
|
|
import (
|
|
|
"encoding/xml"
|
|
|
+ "errors"
|
|
|
"fmt"
|
|
|
"reflect"
|
|
|
"strconv"
|
|
|
@@ -29,18 +30,18 @@ const (
|
|
|
STCellFormulaTypeShared = "shared"
|
|
|
)
|
|
|
|
|
|
-// mergeCellsParser provides a function to check merged cells in worksheet by
|
|
|
-// given axis.
|
|
|
-func (f *File) mergeCellsParser(xlsx *xlsxWorksheet, axis string) string {
|
|
|
- axis = strings.ToUpper(axis)
|
|
|
- if xlsx.MergeCells != nil {
|
|
|
- for i := 0; i < len(xlsx.MergeCells.Cells); i++ {
|
|
|
- if checkCellInArea(axis, xlsx.MergeCells.Cells[i].Ref) {
|
|
|
- axis = strings.Split(xlsx.MergeCells.Cells[i].Ref, ":")[0]
|
|
|
- }
|
|
|
+// GetCellValue provides a function to get formatted value from cell by given
|
|
|
+// worksheet name and axis in XLSX file. 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 (f *File) GetCellValue(sheet, axis string) string {
|
|
|
+ return f.getCellStringFunc(sheet, axis, func(x *xlsxWorksheet, c *xlsxC) (string, bool) {
|
|
|
+ val, err := c.getValueFrom(f, f.sharedStringsReader())
|
|
|
+ if err != nil {
|
|
|
+ panic(err) // Fail fast to avoid future side effects!
|
|
|
}
|
|
|
- }
|
|
|
- return axis
|
|
|
+ return val, true
|
|
|
+ })
|
|
|
}
|
|
|
|
|
|
// SetCellValue provides a function to set value of a cell. The following
|
|
|
@@ -68,256 +69,169 @@ func (f *File) mergeCellsParser(xlsx *xlsxWorksheet, axis string) string {
|
|
|
// Note that default date format is m/d/yy h:mm of time.Time type value. You can
|
|
|
// set numbers format by SetCellStyle() method.
|
|
|
func (f *File) SetCellValue(sheet, axis string, value interface{}) {
|
|
|
- switch t := value.(type) {
|
|
|
- case float32:
|
|
|
- f.SetCellDefault(sheet, axis, strconv.FormatFloat(float64(value.(float32)), 'f', -1, 32))
|
|
|
- case float64:
|
|
|
- f.SetCellDefault(sheet, axis, strconv.FormatFloat(float64(value.(float64)), 'f', -1, 64))
|
|
|
- case string:
|
|
|
- f.SetCellStr(sheet, axis, t)
|
|
|
- case []byte:
|
|
|
- f.SetCellStr(sheet, axis, string(t))
|
|
|
- case time.Duration:
|
|
|
- f.SetCellDefault(sheet, axis, strconv.FormatFloat(float64(value.(time.Duration).Seconds()/86400), 'f', -1, 32))
|
|
|
- f.setDefaultTimeStyle(sheet, axis, 21)
|
|
|
- case time.Time:
|
|
|
- f.SetCellDefault(sheet, axis, strconv.FormatFloat(float64(timeToExcelTime(timeToUTCTime(value.(time.Time)))), 'f', -1, 64))
|
|
|
- f.setDefaultTimeStyle(sheet, axis, 22)
|
|
|
- case nil:
|
|
|
- f.SetCellStr(sheet, axis, "")
|
|
|
- case bool:
|
|
|
- f.SetCellBool(sheet, axis, bool(value.(bool)))
|
|
|
- default:
|
|
|
- f.setCellIntValue(sheet, axis, value)
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-// setCellIntValue provides a function to set int value of a cell.
|
|
|
-func (f *File) setCellIntValue(sheet, axis string, value interface{}) {
|
|
|
- switch value.(type) {
|
|
|
+ switch v := value.(type) {
|
|
|
case int:
|
|
|
- f.SetCellInt(sheet, axis, value.(int))
|
|
|
+ f.SetCellInt(sheet, axis, v)
|
|
|
case int8:
|
|
|
- f.SetCellInt(sheet, axis, int(value.(int8)))
|
|
|
+ f.SetCellInt(sheet, axis, int(v))
|
|
|
case int16:
|
|
|
- f.SetCellInt(sheet, axis, int(value.(int16)))
|
|
|
+ f.SetCellInt(sheet, axis, int(v))
|
|
|
case int32:
|
|
|
- f.SetCellInt(sheet, axis, int(value.(int32)))
|
|
|
+ f.SetCellInt(sheet, axis, int(v))
|
|
|
case int64:
|
|
|
- f.SetCellInt(sheet, axis, int(value.(int64)))
|
|
|
+ f.SetCellInt(sheet, axis, int(v))
|
|
|
case uint:
|
|
|
- f.SetCellInt(sheet, axis, int(value.(uint)))
|
|
|
+ f.SetCellInt(sheet, axis, int(v))
|
|
|
case uint8:
|
|
|
- f.SetCellInt(sheet, axis, int(value.(uint8)))
|
|
|
+ f.SetCellInt(sheet, axis, int(v))
|
|
|
case uint16:
|
|
|
- f.SetCellInt(sheet, axis, int(value.(uint16)))
|
|
|
+ f.SetCellInt(sheet, axis, int(v))
|
|
|
case uint32:
|
|
|
- f.SetCellInt(sheet, axis, int(value.(uint32)))
|
|
|
+ f.SetCellInt(sheet, axis, int(v))
|
|
|
case uint64:
|
|
|
- f.SetCellInt(sheet, axis, int(value.(uint64)))
|
|
|
+ f.SetCellInt(sheet, axis, int(v))
|
|
|
+ case float32:
|
|
|
+ f.SetCellDefault(sheet, axis, strconv.FormatFloat(float64(v), 'f', -1, 32))
|
|
|
+ case float64:
|
|
|
+ f.SetCellDefault(sheet, axis, strconv.FormatFloat(v, 'f', -1, 64))
|
|
|
+ case string:
|
|
|
+ f.SetCellStr(sheet, axis, v)
|
|
|
+ case []byte:
|
|
|
+ f.SetCellStr(sheet, axis, string(v))
|
|
|
+ case time.Duration:
|
|
|
+ f.SetCellDefault(sheet, axis, strconv.FormatFloat(v.Seconds()/86400.0, 'f', -1, 32))
|
|
|
+ f.setDefaultTimeStyle(sheet, axis, 21)
|
|
|
+ case time.Time:
|
|
|
+ vv := timeToExcelTime(v)
|
|
|
+ if vv > 0 {
|
|
|
+ f.SetCellDefault(sheet, axis, strconv.FormatFloat(timeToExcelTime(v), 'f', -1, 64))
|
|
|
+ f.setDefaultTimeStyle(sheet, axis, 22)
|
|
|
+ } else {
|
|
|
+ f.SetCellStr(sheet, axis, v.Format(time.RFC3339Nano))
|
|
|
+ }
|
|
|
+ case bool:
|
|
|
+ f.SetCellBool(sheet, axis, v)
|
|
|
+ case nil:
|
|
|
+ f.SetCellStr(sheet, axis, "")
|
|
|
default:
|
|
|
f.SetCellStr(sheet, axis, fmt.Sprintf("%v", value))
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-// SetCellBool provides a function to set bool type value of a cell by given
|
|
|
+// SetCellInt provides a function to set int type value of a cell by given
|
|
|
// worksheet name, cell coordinates and cell value.
|
|
|
-func (f *File) SetCellBool(sheet, axis string, value bool) {
|
|
|
+func (f *File) SetCellInt(sheet, axis string, value int) {
|
|
|
xlsx := f.workSheetReader(sheet)
|
|
|
- axis = f.mergeCellsParser(xlsx, axis)
|
|
|
- col := string(strings.Map(letterOnlyMapF, axis))
|
|
|
- row, err := strconv.Atoi(strings.Map(intOnlyMapF, axis))
|
|
|
- if err != nil {
|
|
|
- return
|
|
|
- }
|
|
|
- xAxis := row - 1
|
|
|
- yAxis := TitleToNumber(col)
|
|
|
-
|
|
|
- rows := xAxis + 1
|
|
|
- cell := yAxis + 1
|
|
|
-
|
|
|
- completeRow(xlsx, rows, cell)
|
|
|
- completeCol(xlsx, rows, cell)
|
|
|
+ cellData, col, _ := f.prepareCell(xlsx, sheet, axis)
|
|
|
+ cellData.S = f.prepareCellStyle(xlsx, col, cellData.S)
|
|
|
+ cellData.T = ""
|
|
|
+ cellData.V = strconv.Itoa(value)
|
|
|
+}
|
|
|
|
|
|
- xlsx.SheetData.Row[xAxis].C[yAxis].S = f.prepareCellStyle(xlsx, cell, xlsx.SheetData.Row[xAxis].C[yAxis].S)
|
|
|
- xlsx.SheetData.Row[xAxis].C[yAxis].T = "b"
|
|
|
+// SetCellBool provides a function to set bool type value of a cell by given
|
|
|
+// worksheet name, cell name and cell value.
|
|
|
+func (f *File) SetCellBool(sheet, axis string, value bool) {
|
|
|
+ xlsx := f.workSheetReader(sheet)
|
|
|
+ cellData, col, _ := f.prepareCell(xlsx, sheet, axis)
|
|
|
+ cellData.S = f.prepareCellStyle(xlsx, col, cellData.S)
|
|
|
+ cellData.T = "b"
|
|
|
if value {
|
|
|
- xlsx.SheetData.Row[xAxis].C[yAxis].V = "1"
|
|
|
+ cellData.V = "1"
|
|
|
} else {
|
|
|
- xlsx.SheetData.Row[xAxis].C[yAxis].V = "0"
|
|
|
+ cellData.V = "0"
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-// GetCellValue provides a function to get formatted value from cell by given
|
|
|
-// worksheet name and axis in XLSX file. 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 (f *File) GetCellValue(sheet, axis string) string {
|
|
|
+// SetCellStr provides a function to set string type value of a cell. Total
|
|
|
+// number of characters that a cell can contain 32767 characters.
|
|
|
+func (f *File) SetCellStr(sheet, axis, value string) {
|
|
|
xlsx := f.workSheetReader(sheet)
|
|
|
- axis = f.mergeCellsParser(xlsx, axis)
|
|
|
- row, err := strconv.Atoi(strings.Map(intOnlyMapF, axis))
|
|
|
- if err != nil {
|
|
|
- return ""
|
|
|
- }
|
|
|
- xAxis := row - 1
|
|
|
- rows := len(xlsx.SheetData.Row)
|
|
|
- if rows > 1 {
|
|
|
- lastRow := xlsx.SheetData.Row[rows-1].R
|
|
|
- if lastRow >= rows {
|
|
|
- rows = lastRow
|
|
|
- }
|
|
|
- }
|
|
|
- if rows < xAxis {
|
|
|
- return ""
|
|
|
- }
|
|
|
- for k := range xlsx.SheetData.Row {
|
|
|
- if xlsx.SheetData.Row[k].R == row {
|
|
|
- for i := range xlsx.SheetData.Row[k].C {
|
|
|
- if axis == xlsx.SheetData.Row[k].C[i].R {
|
|
|
- val, _ := xlsx.SheetData.Row[k].C[i].getValueFrom(f, f.sharedStringsReader())
|
|
|
- return val
|
|
|
- }
|
|
|
- }
|
|
|
+ cellData, col, _ := f.prepareCell(xlsx, sheet, axis)
|
|
|
+
|
|
|
+ // Leading space(s) character detection.
|
|
|
+ if len(value) > 0 && value[0] == 32 {
|
|
|
+ cellData.XMLSpace = xml.Attr{
|
|
|
+ Name: xml.Name{Space: NameSpaceXML, Local: "space"},
|
|
|
+ Value: "preserve",
|
|
|
}
|
|
|
}
|
|
|
- return ""
|
|
|
-}
|
|
|
|
|
|
-// formattedValue provides a function to returns a value after formatted. 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 (f *File) formattedValue(s int, v string) string {
|
|
|
- if s == 0 {
|
|
|
- return v
|
|
|
- }
|
|
|
- styleSheet := f.stylesReader()
|
|
|
- ok := builtInNumFmtFunc[styleSheet.CellXfs.Xf[s].NumFmtID]
|
|
|
- if ok != nil {
|
|
|
- return ok(styleSheet.CellXfs.Xf[s].NumFmtID, v)
|
|
|
- }
|
|
|
- return v
|
|
|
+ cellData.S = f.prepareCellStyle(xlsx, col, cellData.S)
|
|
|
+ cellData.T = "str"
|
|
|
+ cellData.V = value
|
|
|
}
|
|
|
|
|
|
-// GetCellStyle provides a function to get cell style index by given worksheet
|
|
|
-// name and cell coordinates.
|
|
|
-func (f *File) GetCellStyle(sheet, axis string) int {
|
|
|
+// SetCellDefault provides a function to set string type value of a cell as
|
|
|
+// default format without escaping the cell.
|
|
|
+func (f *File) SetCellDefault(sheet, axis, value string) {
|
|
|
xlsx := f.workSheetReader(sheet)
|
|
|
- axis = f.mergeCellsParser(xlsx, axis)
|
|
|
- col := string(strings.Map(letterOnlyMapF, axis))
|
|
|
- row, err := strconv.Atoi(strings.Map(intOnlyMapF, axis))
|
|
|
- if err != nil {
|
|
|
- return 0
|
|
|
- }
|
|
|
- xAxis := row - 1
|
|
|
- yAxis := TitleToNumber(col)
|
|
|
-
|
|
|
- rows := xAxis + 1
|
|
|
- cell := yAxis + 1
|
|
|
-
|
|
|
- completeRow(xlsx, rows, cell)
|
|
|
- completeCol(xlsx, rows, cell)
|
|
|
-
|
|
|
- return f.prepareCellStyle(xlsx, cell, xlsx.SheetData.Row[xAxis].C[yAxis].S)
|
|
|
+ cellData, col, _ := f.prepareCell(xlsx, sheet, axis)
|
|
|
+ cellData.S = f.prepareCellStyle(xlsx, col, cellData.S)
|
|
|
+ cellData.T = ""
|
|
|
+ cellData.V = value
|
|
|
}
|
|
|
|
|
|
// GetCellFormula provides a function to get formula from cell by given
|
|
|
// worksheet name and axis in XLSX file.
|
|
|
func (f *File) GetCellFormula(sheet, axis string) string {
|
|
|
- xlsx := f.workSheetReader(sheet)
|
|
|
- axis = f.mergeCellsParser(xlsx, axis)
|
|
|
- row, err := strconv.Atoi(strings.Map(intOnlyMapF, axis))
|
|
|
- if err != nil {
|
|
|
- return ""
|
|
|
- }
|
|
|
- xAxis := row - 1
|
|
|
- rows := len(xlsx.SheetData.Row)
|
|
|
- if rows > 1 {
|
|
|
- lastRow := xlsx.SheetData.Row[rows-1].R
|
|
|
- if lastRow >= rows {
|
|
|
- rows = lastRow
|
|
|
- }
|
|
|
- }
|
|
|
- if rows < xAxis {
|
|
|
- return ""
|
|
|
- }
|
|
|
- for k := range xlsx.SheetData.Row {
|
|
|
- if xlsx.SheetData.Row[k].R == row {
|
|
|
- for i := range xlsx.SheetData.Row[k].C {
|
|
|
- if axis == xlsx.SheetData.Row[k].C[i].R {
|
|
|
- if xlsx.SheetData.Row[k].C[i].F == nil {
|
|
|
- continue
|
|
|
- }
|
|
|
- if xlsx.SheetData.Row[k].C[i].F.T == STCellFormulaTypeShared {
|
|
|
- return getSharedForumula(xlsx, xlsx.SheetData.Row[k].C[i].F.Si)
|
|
|
- }
|
|
|
- return xlsx.SheetData.Row[k].C[i].F.Content
|
|
|
- }
|
|
|
- }
|
|
|
+ return f.getCellStringFunc(sheet, axis, func(x *xlsxWorksheet, c *xlsxC) (string, bool) {
|
|
|
+ if c.F == nil {
|
|
|
+ return "", false
|
|
|
}
|
|
|
- }
|
|
|
- return ""
|
|
|
-}
|
|
|
-
|
|
|
-// getSharedForumula find a cell contains the same formula as another cell,
|
|
|
-// the "shared" value can be used for the t attribute and the si attribute can
|
|
|
-// be used to refer to the cell containing the formula. Two formulas are
|
|
|
-// considered to be the same when their respective representations in
|
|
|
-// R1C1-reference notation, are the same.
|
|
|
-//
|
|
|
-// Note that this function not validate ref tag to check the cell if or not in
|
|
|
-// allow area, and always return origin shared formula.
|
|
|
-func getSharedForumula(xlsx *xlsxWorksheet, si string) string {
|
|
|
- for k := range xlsx.SheetData.Row {
|
|
|
- for i := range xlsx.SheetData.Row[k].C {
|
|
|
- if xlsx.SheetData.Row[k].C[i].F == nil {
|
|
|
- continue
|
|
|
- }
|
|
|
- if xlsx.SheetData.Row[k].C[i].F.T != STCellFormulaTypeShared {
|
|
|
- continue
|
|
|
- }
|
|
|
- if xlsx.SheetData.Row[k].C[i].F.Si != si {
|
|
|
- continue
|
|
|
- }
|
|
|
- if xlsx.SheetData.Row[k].C[i].F.Ref != "" {
|
|
|
- return xlsx.SheetData.Row[k].C[i].F.Content
|
|
|
- }
|
|
|
+ if c.F.T == STCellFormulaTypeShared {
|
|
|
+ return getSharedForumula(x, c.F.Si), true
|
|
|
}
|
|
|
- }
|
|
|
- return ""
|
|
|
+ return c.F.Content, true
|
|
|
+ })
|
|
|
}
|
|
|
|
|
|
// SetCellFormula provides a function to set cell formula by given string and
|
|
|
// worksheet name.
|
|
|
func (f *File) SetCellFormula(sheet, axis, formula string) {
|
|
|
xlsx := f.workSheetReader(sheet)
|
|
|
- axis = f.mergeCellsParser(xlsx, axis)
|
|
|
- col := string(strings.Map(letterOnlyMapF, axis))
|
|
|
- row, err := strconv.Atoi(strings.Map(intOnlyMapF, axis))
|
|
|
- if err != nil {
|
|
|
- return
|
|
|
- }
|
|
|
- xAxis := row - 1
|
|
|
- yAxis := TitleToNumber(col)
|
|
|
-
|
|
|
- rows := xAxis + 1
|
|
|
- cell := yAxis + 1
|
|
|
-
|
|
|
- completeRow(xlsx, rows, cell)
|
|
|
- completeCol(xlsx, rows, cell)
|
|
|
+ cellData, _, _ := f.prepareCell(xlsx, sheet, axis)
|
|
|
|
|
|
if formula == "" {
|
|
|
- xlsx.SheetData.Row[xAxis].C[yAxis].F = nil
|
|
|
+ cellData.F = nil
|
|
|
f.deleteCalcChain(axis)
|
|
|
return
|
|
|
}
|
|
|
- if xlsx.SheetData.Row[xAxis].C[yAxis].F != nil {
|
|
|
- xlsx.SheetData.Row[xAxis].C[yAxis].F.Content = formula
|
|
|
+
|
|
|
+ if cellData.F != nil {
|
|
|
+ cellData.F.Content = formula
|
|
|
} else {
|
|
|
- f := xlsxF{
|
|
|
- Content: formula,
|
|
|
+ cellData.F = &xlsxF{Content: formula}
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// GetCellHyperLink provides a function to get cell hyperlink by given
|
|
|
+// worksheet name and axis. Boolean type value link will be ture if the cell
|
|
|
+// has a hyperlink and the target is the address of the hyperlink. Otherwise,
|
|
|
+// the value of link will be false and the value of the target will be a blank
|
|
|
+// string. For example get hyperlink of Sheet1!H6:
|
|
|
+//
|
|
|
+// link, target := xlsx.GetCellHyperLink("Sheet1", "H6")
|
|
|
+//
|
|
|
+func (f *File) GetCellHyperLink(sheet, axis string) (bool, string) {
|
|
|
+ // Check for correct cell name
|
|
|
+ if _, _, err := SplitCellName(axis); err != nil {
|
|
|
+ panic(err) // Fail fast to avoid possible future side effects
|
|
|
+ }
|
|
|
+
|
|
|
+ xlsx := f.workSheetReader(sheet)
|
|
|
+ axis = f.mergeCellsParser(xlsx, axis)
|
|
|
+
|
|
|
+ if xlsx.Hyperlinks != nil {
|
|
|
+ for _, link := range xlsx.Hyperlinks.Hyperlink {
|
|
|
+ if link.Ref == axis {
|
|
|
+ if link.RID != "" {
|
|
|
+ return true, f.getSheetRelationshipsTargetByID(sheet, link.RID)
|
|
|
+ }
|
|
|
+ return true, link.Location
|
|
|
+ }
|
|
|
}
|
|
|
- xlsx.SheetData.Row[xAxis].C[yAxis].F = &f
|
|
|
}
|
|
|
+ return false, ""
|
|
|
}
|
|
|
|
|
|
// SetCellHyperLink provides a function to set cell hyperlink by given
|
|
|
@@ -335,53 +249,36 @@ func (f *File) SetCellFormula(sheet, axis, formula string) {
|
|
|
// xlsx.SetCellHyperLink("Sheet1", "A3", "Sheet1!A40", "Location")
|
|
|
//
|
|
|
func (f *File) SetCellHyperLink(sheet, axis, link, linkType string) {
|
|
|
- xlsx := f.workSheetReader(sheet)
|
|
|
- axis = f.mergeCellsParser(xlsx, axis)
|
|
|
- linkTypes := map[string]xlsxHyperlink{
|
|
|
- "External": {},
|
|
|
- "Location": {Location: link},
|
|
|
+ // Check for correct cell name
|
|
|
+ if _, _, err := SplitCellName(axis); err != nil {
|
|
|
+ panic(err) // Fail fast to avoid possible future side effects
|
|
|
}
|
|
|
- hyperlink, ok := linkTypes[linkType]
|
|
|
- if !ok || axis == "" {
|
|
|
- return
|
|
|
- }
|
|
|
- hyperlink.Ref = axis
|
|
|
- if linkType == "External" {
|
|
|
- rID := f.addSheetRelationships(sheet, SourceRelationshipHyperLink, link, linkType)
|
|
|
- hyperlink.RID = "rId" + strconv.Itoa(rID)
|
|
|
- }
|
|
|
- if xlsx.Hyperlinks == nil {
|
|
|
- xlsx.Hyperlinks = &xlsxHyperlinks{}
|
|
|
- }
|
|
|
- xlsx.Hyperlinks.Hyperlink = append(xlsx.Hyperlinks.Hyperlink, hyperlink)
|
|
|
-}
|
|
|
|
|
|
-// GetCellHyperLink provides a function to get cell hyperlink by given
|
|
|
-// worksheet name and axis. Boolean type value link will be ture if the cell
|
|
|
-// has a hyperlink and the target is the address of the hyperlink. Otherwise,
|
|
|
-// the value of link will be false and the value of the target will be a blank
|
|
|
-// string. For example get hyperlink of Sheet1!H6:
|
|
|
-//
|
|
|
-// link, target := xlsx.GetCellHyperLink("Sheet1", "H6")
|
|
|
-//
|
|
|
-func (f *File) GetCellHyperLink(sheet, axis string) (bool, string) {
|
|
|
- var link bool
|
|
|
- var target string
|
|
|
xlsx := f.workSheetReader(sheet)
|
|
|
axis = f.mergeCellsParser(xlsx, axis)
|
|
|
- if xlsx.Hyperlinks == nil || axis == "" {
|
|
|
- return link, target
|
|
|
- }
|
|
|
- for h := range xlsx.Hyperlinks.Hyperlink {
|
|
|
- if xlsx.Hyperlinks.Hyperlink[h].Ref == axis {
|
|
|
- link = true
|
|
|
- target = xlsx.Hyperlinks.Hyperlink[h].Location
|
|
|
- if xlsx.Hyperlinks.Hyperlink[h].RID != "" {
|
|
|
- target = f.getSheetRelationshipsTargetByID(sheet, xlsx.Hyperlinks.Hyperlink[h].RID)
|
|
|
- }
|
|
|
+
|
|
|
+ var linkData xlsxHyperlink
|
|
|
+
|
|
|
+ switch linkType {
|
|
|
+ case "External":
|
|
|
+ linkData = xlsxHyperlink{
|
|
|
+ Ref: axis,
|
|
|
}
|
|
|
+ rID := f.addSheetRelationships(sheet, SourceRelationshipHyperLink, link, linkType)
|
|
|
+ linkData.RID = "rId" + strconv.Itoa(rID)
|
|
|
+ case "Location":
|
|
|
+ linkData = xlsxHyperlink{
|
|
|
+ Ref: axis,
|
|
|
+ Location: link,
|
|
|
+ }
|
|
|
+ default:
|
|
|
+ panic(fmt.Errorf("invalid link type %q", linkType))
|
|
|
+ }
|
|
|
+
|
|
|
+ if xlsx.Hyperlinks == nil {
|
|
|
+ xlsx.Hyperlinks = new(xlsxHyperlinks)
|
|
|
}
|
|
|
- return link, target
|
|
|
+ xlsx.Hyperlinks.Hyperlink = append(xlsx.Hyperlinks.Hyperlink, linkData)
|
|
|
}
|
|
|
|
|
|
// MergeCell provides a function to merge cells by given coordinate area and
|
|
|
@@ -392,213 +289,214 @@ func (f *File) GetCellHyperLink(sheet, axis string) (bool, string) {
|
|
|
// If you create a merged cell that overlaps with another existing merged cell,
|
|
|
// those merged cells that already exist will be removed.
|
|
|
func (f *File) MergeCell(sheet, hcell, vcell string) {
|
|
|
- if hcell == vcell {
|
|
|
- return
|
|
|
+ hcol, hrow, err := CellNameToCoordinates(hcell)
|
|
|
+ if err != nil {
|
|
|
+ panic(err)
|
|
|
}
|
|
|
|
|
|
- hcell = strings.ToUpper(hcell)
|
|
|
- vcell = strings.ToUpper(vcell)
|
|
|
-
|
|
|
- // Coordinate conversion, convert C1:B3 to 2,0,1,2.
|
|
|
- hcol := string(strings.Map(letterOnlyMapF, hcell))
|
|
|
- hrow, _ := strconv.Atoi(strings.Map(intOnlyMapF, hcell))
|
|
|
- hyAxis := hrow - 1
|
|
|
- hxAxis := TitleToNumber(hcol)
|
|
|
+ vcol, vrow, err := CellNameToCoordinates(vcell)
|
|
|
+ if err != nil {
|
|
|
+ panic(err)
|
|
|
+ }
|
|
|
|
|
|
- vcol := string(strings.Map(letterOnlyMapF, vcell))
|
|
|
- vrow, _ := strconv.Atoi(strings.Map(intOnlyMapF, vcell))
|
|
|
- vyAxis := vrow - 1
|
|
|
- vxAxis := TitleToNumber(vcol)
|
|
|
+ if hcol == vcol && hrow == vrow {
|
|
|
+ return
|
|
|
+ }
|
|
|
|
|
|
- if vxAxis < hxAxis {
|
|
|
- hcell, vcell = vcell, hcell
|
|
|
- vxAxis, hxAxis = hxAxis, vxAxis
|
|
|
+ if vcol < hcol {
|
|
|
+ hcol, vcol = vcol, hcol
|
|
|
}
|
|
|
|
|
|
- if vyAxis < hyAxis {
|
|
|
- hcell, vcell = vcell, hcell
|
|
|
- vyAxis, hyAxis = hyAxis, vyAxis
|
|
|
+ if vrow < hrow {
|
|
|
+ hrow, vrow = vrow, hrow
|
|
|
}
|
|
|
|
|
|
+ hcell, _ = CoordinatesToCellName(hcol, hrow)
|
|
|
+ vcell, _ = CoordinatesToCellName(vcol, vrow)
|
|
|
+
|
|
|
xlsx := f.workSheetReader(sheet)
|
|
|
if xlsx.MergeCells != nil {
|
|
|
- mergeCell := xlsxMergeCell{}
|
|
|
- // Correct the coordinate area, such correct C1:B3 to B1:C3.
|
|
|
- mergeCell.Ref = ToAlphaString(hxAxis) + strconv.Itoa(hyAxis+1) + ":" + ToAlphaString(vxAxis) + strconv.Itoa(vyAxis+1)
|
|
|
+ ref := hcell + ":" + vcell
|
|
|
+ cells := make([]*xlsxMergeCell, 0, len(xlsx.MergeCells.Cells))
|
|
|
+
|
|
|
// Delete the merged cells of the overlapping area.
|
|
|
- for i := 0; i < len(xlsx.MergeCells.Cells); i++ {
|
|
|
- if checkCellInArea(hcell, xlsx.MergeCells.Cells[i].Ref) || checkCellInArea(strings.Split(xlsx.MergeCells.Cells[i].Ref, ":")[0], mergeCell.Ref) {
|
|
|
- xlsx.MergeCells.Cells = append(xlsx.MergeCells.Cells[:i], xlsx.MergeCells.Cells[i+1:]...)
|
|
|
- } else if checkCellInArea(vcell, xlsx.MergeCells.Cells[i].Ref) || checkCellInArea(strings.Split(xlsx.MergeCells.Cells[i].Ref, ":")[1], mergeCell.Ref) {
|
|
|
- xlsx.MergeCells.Cells = append(xlsx.MergeCells.Cells[:i], xlsx.MergeCells.Cells[i+1:]...)
|
|
|
+ for _, cellData := range xlsx.MergeCells.Cells {
|
|
|
+ cc := strings.Split(cellData.Ref, ":")
|
|
|
+ if len(cc) != 2 {
|
|
|
+ panic(fmt.Errorf("invalid area %q", cellData.Ref))
|
|
|
+ }
|
|
|
+
|
|
|
+ if !checkCellInArea(hcell, cellData.Ref) && !checkCellInArea(vcell, cellData.Ref) &&
|
|
|
+ !checkCellInArea(cc[0], ref) && !checkCellInArea(cc[1], ref) {
|
|
|
+ cells = append(cells, cellData)
|
|
|
}
|
|
|
}
|
|
|
- xlsx.MergeCells.Cells = append(xlsx.MergeCells.Cells, &mergeCell)
|
|
|
+ cells = append(xlsx.MergeCells.Cells, &xlsxMergeCell{Ref: ref})
|
|
|
+ xlsx.MergeCells.Cells = cells
|
|
|
} else {
|
|
|
- mergeCell := xlsxMergeCell{}
|
|
|
- // Correct the coordinate area, such correct C1:B3 to B1:C3.
|
|
|
- mergeCell.Ref = ToAlphaString(hxAxis) + strconv.Itoa(hyAxis+1) + ":" + ToAlphaString(vxAxis) + strconv.Itoa(vyAxis+1)
|
|
|
- mergeCells := xlsxMergeCells{}
|
|
|
- mergeCells.Cells = append(mergeCells.Cells, &mergeCell)
|
|
|
- xlsx.MergeCells = &mergeCells
|
|
|
+ xlsx.MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: hcell + ":" + vcell}}}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-// SetCellInt provides a function to set int type value of a cell by given
|
|
|
-// worksheet name, cell coordinates and cell value.
|
|
|
-func (f *File) SetCellInt(sheet, axis string, value int) {
|
|
|
- xlsx := f.workSheetReader(sheet)
|
|
|
- axis = f.mergeCellsParser(xlsx, axis)
|
|
|
- col := string(strings.Map(letterOnlyMapF, axis))
|
|
|
- row, err := strconv.Atoi(strings.Map(intOnlyMapF, axis))
|
|
|
+// SetSheetRow writes an array to row by given worksheet name, starting
|
|
|
+// coordinate and a pointer to array type 'slice'. For example, writes an
|
|
|
+// array to row 6 start with the cell B6 on Sheet1:
|
|
|
+//
|
|
|
+// xlsx.SetSheetRow("Sheet1", "B6", &[]interface{}{"1", nil, 2})
|
|
|
+//
|
|
|
+func (f *File) SetSheetRow(sheet, axis string, slice interface{}) {
|
|
|
+ col, row, err := CellNameToCoordinates(axis)
|
|
|
if err != nil {
|
|
|
- return
|
|
|
+ panic(err) // Fail fast to avoid future side effects!
|
|
|
}
|
|
|
- xAxis := row - 1
|
|
|
- yAxis := TitleToNumber(col)
|
|
|
-
|
|
|
- rows := xAxis + 1
|
|
|
- cell := yAxis + 1
|
|
|
-
|
|
|
- completeRow(xlsx, rows, cell)
|
|
|
- completeCol(xlsx, rows, cell)
|
|
|
|
|
|
- xlsx.SheetData.Row[xAxis].C[yAxis].S = f.prepareCellStyle(xlsx, cell, xlsx.SheetData.Row[xAxis].C[yAxis].S)
|
|
|
- xlsx.SheetData.Row[xAxis].C[yAxis].T = ""
|
|
|
- xlsx.SheetData.Row[xAxis].C[yAxis].V = strconv.Itoa(value)
|
|
|
-}
|
|
|
+ // Make sure 'slice' is a Ptr to Slice
|
|
|
+ v := reflect.ValueOf(slice)
|
|
|
+ if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Slice {
|
|
|
+ panic(errors.New("pointer to slice expected")) // Fail fast to avoid future side effects!
|
|
|
+ }
|
|
|
+ v = v.Elem()
|
|
|
|
|
|
-// prepareCellStyle provides a function to prepare style index of cell in
|
|
|
-// worksheet by given column index and style index.
|
|
|
-func (f *File) prepareCellStyle(xlsx *xlsxWorksheet, col, style int) int {
|
|
|
- if xlsx.Cols != nil && style == 0 {
|
|
|
- for _, v := range xlsx.Cols.Col {
|
|
|
- if v.Min <= col && col <= v.Max {
|
|
|
- style = v.Style
|
|
|
- }
|
|
|
+ for i := 0; i < v.Len(); i++ {
|
|
|
+ cell, err := CoordinatesToCellName(col+i, row)
|
|
|
+ // Error should never happens here. But keep ckecking to early detect regresions
|
|
|
+ // if it will be introduced in furure
|
|
|
+ if err != nil {
|
|
|
+ panic(err) // Fail fast to avoid future side effects!
|
|
|
}
|
|
|
+ f.SetCellValue(sheet, cell, v.Index(i).Interface())
|
|
|
}
|
|
|
- return style
|
|
|
}
|
|
|
|
|
|
-// SetCellStr provides a function to set string type value of a cell. Total
|
|
|
-// number of characters that a cell can contain 32767 characters.
|
|
|
-func (f *File) SetCellStr(sheet, axis, value string) {
|
|
|
- xlsx := f.workSheetReader(sheet)
|
|
|
- axis = f.mergeCellsParser(xlsx, axis)
|
|
|
- if len(value) > 32767 {
|
|
|
- value = value[0:32767]
|
|
|
- }
|
|
|
- col := string(strings.Map(letterOnlyMapF, axis))
|
|
|
- row, err := strconv.Atoi(strings.Map(intOnlyMapF, axis))
|
|
|
+// getCellInfo does common preparation for all SetCell* methods.
|
|
|
+func (f *File) prepareCell(xlsx *xlsxWorksheet, sheet, cell string) (*xlsxC, int, int) {
|
|
|
+ cell = f.mergeCellsParser(xlsx, cell)
|
|
|
+
|
|
|
+ col, row, err := CellNameToCoordinates(cell)
|
|
|
if err != nil {
|
|
|
- return
|
|
|
+ panic(err) // Fail fast and prevent future side effects
|
|
|
}
|
|
|
- xAxis := row - 1
|
|
|
- yAxis := TitleToNumber(col)
|
|
|
-
|
|
|
- rows := xAxis + 1
|
|
|
- cell := yAxis + 1
|
|
|
|
|
|
- completeRow(xlsx, rows, cell)
|
|
|
- completeCol(xlsx, rows, cell)
|
|
|
+ prepareSheetXML(xlsx, col, row)
|
|
|
|
|
|
- // Leading space(s) character detection.
|
|
|
- if len(value) > 0 {
|
|
|
- if value[0] == 32 {
|
|
|
- xlsx.SheetData.Row[xAxis].C[yAxis].XMLSpace = xml.Attr{
|
|
|
- Name: xml.Name{Space: NameSpaceXML, Local: "space"},
|
|
|
- Value: "preserve",
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- xlsx.SheetData.Row[xAxis].C[yAxis].S = f.prepareCellStyle(xlsx, cell, xlsx.SheetData.Row[xAxis].C[yAxis].S)
|
|
|
- xlsx.SheetData.Row[xAxis].C[yAxis].T = "str"
|
|
|
- xlsx.SheetData.Row[xAxis].C[yAxis].V = value
|
|
|
+ return &xlsx.SheetData.Row[row-1].C[col-1], col, row
|
|
|
}
|
|
|
|
|
|
-// SetCellDefault provides a function to set string type value of a cell as
|
|
|
-// default format without escaping the cell.
|
|
|
-func (f *File) SetCellDefault(sheet, axis, value string) {
|
|
|
+// getCellStringFunc does common value extraction workflow for all GetCell* methods.
|
|
|
+// Passed function implements specific part of required logic.
|
|
|
+func (f *File) getCellStringFunc(sheet, axis string, fn func(x *xlsxWorksheet, c *xlsxC) (string, bool)) string {
|
|
|
xlsx := f.workSheetReader(sheet)
|
|
|
axis = f.mergeCellsParser(xlsx, axis)
|
|
|
- col := string(strings.Map(letterOnlyMapF, axis))
|
|
|
- row, err := strconv.Atoi(strings.Map(intOnlyMapF, axis))
|
|
|
+
|
|
|
+ _, row, err := CellNameToCoordinates(axis)
|
|
|
if err != nil {
|
|
|
- return
|
|
|
+ panic(err) // Fail fast to avoid future side effects!
|
|
|
}
|
|
|
- xAxis := row - 1
|
|
|
- yAxis := TitleToNumber(col)
|
|
|
|
|
|
- rows := xAxis + 1
|
|
|
- cell := yAxis + 1
|
|
|
+ lastRowNum := 0
|
|
|
+ if l := len(xlsx.SheetData.Row); l > 0 {
|
|
|
+ lastRowNum = xlsx.SheetData.Row[l-1].R
|
|
|
+ }
|
|
|
|
|
|
- completeRow(xlsx, rows, cell)
|
|
|
- completeCol(xlsx, rows, cell)
|
|
|
+ // keep in mind: row starts from 1
|
|
|
+ if row > lastRowNum {
|
|
|
+ return ""
|
|
|
+ }
|
|
|
|
|
|
- xlsx.SheetData.Row[xAxis].C[yAxis].S = f.prepareCellStyle(xlsx, cell, xlsx.SheetData.Row[xAxis].C[yAxis].S)
|
|
|
- xlsx.SheetData.Row[xAxis].C[yAxis].T = ""
|
|
|
- xlsx.SheetData.Row[xAxis].C[yAxis].V = value
|
|
|
+ for rowIdx := range xlsx.SheetData.Row {
|
|
|
+ rowData := &xlsx.SheetData.Row[rowIdx]
|
|
|
+ if rowData.R != row {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ for colIdx := range rowData.C {
|
|
|
+ colData := &rowData.C[colIdx]
|
|
|
+ if axis != colData.R {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ if val, ok := fn(xlsx, colData); ok {
|
|
|
+ return val
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return ""
|
|
|
}
|
|
|
|
|
|
-// SetSheetRow writes an array to row by given worksheet name, starting
|
|
|
-// coordinate and a pointer to array type 'slice'. For example, writes an
|
|
|
-// array to row 6 start with the cell B6 on Sheet1:
|
|
|
-//
|
|
|
-// xlsx.SetSheetRow("Sheet1", "B6", &[]interface{}{"1", nil, 2})
|
|
|
-//
|
|
|
-func (f *File) SetSheetRow(sheet, axis string, slice interface{}) {
|
|
|
- xlsx := f.workSheetReader(sheet)
|
|
|
- axis = f.mergeCellsParser(xlsx, axis)
|
|
|
- col := string(strings.Map(letterOnlyMapF, axis))
|
|
|
- row, err := strconv.Atoi(strings.Map(intOnlyMapF, axis))
|
|
|
- if err != nil {
|
|
|
- return
|
|
|
- }
|
|
|
- // Make sure 'slice' is a Ptr to Slice
|
|
|
- v := reflect.ValueOf(slice)
|
|
|
- if v.Kind() != reflect.Ptr {
|
|
|
- return
|
|
|
+// formattedValue provides a function to returns a value after formatted. 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 (f *File) formattedValue(s int, v string) string {
|
|
|
+ if s == 0 {
|
|
|
+ return v
|
|
|
}
|
|
|
- v = v.Elem()
|
|
|
- if v.Kind() != reflect.Slice {
|
|
|
- return
|
|
|
+ styleSheet := f.stylesReader()
|
|
|
+ ok := builtInNumFmtFunc[styleSheet.CellXfs.Xf[s].NumFmtID]
|
|
|
+ if ok != nil {
|
|
|
+ return ok(styleSheet.CellXfs.Xf[s].NumFmtID, v)
|
|
|
}
|
|
|
+ return v
|
|
|
+}
|
|
|
|
|
|
- xAxis := row - 1
|
|
|
- yAxis := TitleToNumber(col)
|
|
|
-
|
|
|
- rows := xAxis + 1
|
|
|
- cell := yAxis + 1
|
|
|
-
|
|
|
- completeRow(xlsx, rows, cell)
|
|
|
- completeCol(xlsx, rows, cell)
|
|
|
+// prepareCellStyle provides a function to prepare style index of cell in
|
|
|
+// worksheet by given column index and style index.
|
|
|
+func (f *File) prepareCellStyle(xlsx *xlsxWorksheet, col, style int) int {
|
|
|
+ if xlsx.Cols != nil && style == 0 {
|
|
|
+ for _, c := range xlsx.Cols.Col {
|
|
|
+ if c.Min <= col && col <= c.Max {
|
|
|
+ style = c.Style
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return style
|
|
|
+}
|
|
|
|
|
|
- idx := 0
|
|
|
- for i := cell - 1; i < v.Len()+cell-1; i++ {
|
|
|
- c := ToAlphaString(i) + strconv.Itoa(row)
|
|
|
- f.SetCellValue(sheet, c, v.Index(idx).Interface())
|
|
|
- idx++
|
|
|
+// mergeCellsParser provides a function to check merged cells in worksheet by
|
|
|
+// given axis.
|
|
|
+func (f *File) mergeCellsParser(xlsx *xlsxWorksheet, axis string) string {
|
|
|
+ axis = strings.ToUpper(axis)
|
|
|
+ if xlsx.MergeCells != nil {
|
|
|
+ for i := 0; i < len(xlsx.MergeCells.Cells); i++ {
|
|
|
+ if checkCellInArea(axis, xlsx.MergeCells.Cells[i].Ref) {
|
|
|
+ axis = strings.Split(xlsx.MergeCells.Cells[i].Ref, ":")[0]
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
+ return axis
|
|
|
}
|
|
|
|
|
|
// checkCellInArea provides a function to determine if a given coordinate is
|
|
|
// within an area.
|
|
|
func checkCellInArea(cell, area string) bool {
|
|
|
- cell = strings.ToUpper(cell)
|
|
|
- area = strings.ToUpper(area)
|
|
|
+ col, row, err := CellNameToCoordinates(cell)
|
|
|
+ if err != nil {
|
|
|
+ panic(err)
|
|
|
+ }
|
|
|
|
|
|
- ref := strings.Split(area, ":")
|
|
|
- if len(ref) < 2 {
|
|
|
+ rng := strings.Split(area, ":")
|
|
|
+ if len(rng) != 2 {
|
|
|
return false
|
|
|
}
|
|
|
|
|
|
- from := ref[0]
|
|
|
- to := ref[1]
|
|
|
+ firstCol, firtsRow, _ := CellNameToCoordinates(rng[0])
|
|
|
+ lastCol, lastRow, _ := CellNameToCoordinates(rng[1])
|
|
|
|
|
|
- col, row := getCellColRow(cell)
|
|
|
- fromCol, fromRow := getCellColRow(from)
|
|
|
- toCol, toRow := getCellColRow(to)
|
|
|
+ return col >= firstCol && col <= lastCol && row >= firtsRow && row <= lastRow
|
|
|
+}
|
|
|
|
|
|
- return axisLowerOrEqualThan(fromCol, col) && axisLowerOrEqualThan(col, toCol) && axisLowerOrEqualThan(fromRow, row) && axisLowerOrEqualThan(row, toRow)
|
|
|
+// getSharedForumula find a cell contains the same formula as another cell,
|
|
|
+// the "shared" value can be used for the t attribute and the si attribute can
|
|
|
+// be used to refer to the cell containing the formula. Two formulas are
|
|
|
+// considered to be the same when their respective representations in
|
|
|
+// R1C1-reference notation, are the same.
|
|
|
+//
|
|
|
+// Note that this function not validate ref tag to check the cell if or not in
|
|
|
+// allow area, and always return origin shared formula.
|
|
|
+func getSharedForumula(xlsx *xlsxWorksheet, si string) string {
|
|
|
+ for _, r := range xlsx.SheetData.Row {
|
|
|
+ for _, c := range r.C {
|
|
|
+ if c.F != nil && c.F.Ref != "" && c.F.T == STCellFormulaTypeShared && c.F.Si == si {
|
|
|
+ return c.F.Content
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return ""
|
|
|
}
|