diff --git a/README.md b/README.md
index 14a0e97b..b761eb66 100644
--- a/README.md
+++ b/README.md
@@ -2,8 +2,8 @@
# Excelize
-[![Build Status](https://travis-ci.org/xuri/excelize.svg?branch=master)](https://travis-ci.org/xuri/excelize)
-[![Code Coverage](https://codecov.io/gh/xuri/excelize/branch/master/graph/badge.svg)](https://codecov.io/gh/xuri/excelize)
+[![Build Status](https://travis-ci.org/360EntSecGroup-Skylar/excelize.svg?branch=master)](https://travis-ci.org/360EntSecGroup-Skylar/excelize)
+[![Code Coverage](https://codecov.io/gh/360EntSecGroup-Skylar/excelize/branch/master/graph/badge.svg)](https://codecov.io/gh/360EntSecGroup-Skylar/excelize)
[![Go Report Card](https://goreportcard.com/badge/github.com/xuri/excelize)](https://goreportcard.com/report/github.com/xuri/excelize)
[![GoDoc](https://godoc.org/github.com/xuri/excelize?status.svg)](https://godoc.org/github.com/xuri/excelize)
[![Licenses](https://img.shields.io/badge/license-bsd-orange.svg)](https://opensource.org/licenses/BSD-3-Clause)
diff --git a/cell.go b/cell.go
index 34595bcf..f81f36c5 100644
--- a/cell.go
+++ b/cell.go
@@ -1,8 +1,11 @@
package excelize
import (
+ "encoding/xml"
+ "fmt"
"strconv"
"strings"
+ "time"
)
// mergeCellsParser provides function to check merged cells in worksheet by
@@ -19,6 +22,53 @@ func (f *File) mergeCellsParser(xlsx *xlsxWorksheet, axis string) string {
return axis
}
+// SetCellValue provides function to set value of a cell. The following shows
+// the supported data types:
+//
+// int
+// int8
+// int16
+// int32
+// int64
+// float32
+// float64
+// string
+// []byte
+// time.Time
+// nil
+//
+// 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 int:
+ f.SetCellInt(sheet, axis, value.(int))
+ case int8:
+ f.SetCellInt(sheet, axis, int(value.(int8)))
+ case int16:
+ f.SetCellInt(sheet, axis, int(value.(int16)))
+ case int32:
+ f.SetCellInt(sheet, axis, int(value.(int32)))
+ case int64:
+ f.SetCellInt(sheet, axis, int(value.(int64)))
+ 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.Time:
+ f.SetCellDefault(sheet, axis, strconv.FormatFloat(float64(timeToExcelTime(timeToUTCTime(value.(time.Time)))), 'f', -1, 32))
+ f.setDefaultTimeStyle(sheet, axis)
+ case nil:
+ f.SetCellStr(sheet, axis, "")
+ default:
+ f.SetCellStr(sheet, axis, fmt.Sprintf("%v", value))
+ }
+}
+
// GetCellValue provides function to get formatted value from cell by given
// sheet index 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
@@ -77,6 +127,25 @@ func (f *File) formattedValue(s int, v string) string {
return v
}
+// GetCellStyle provides function to get cell style index by given worksheet
+// name and cell coordinates.
+func (f *File) GetCellStyle(sheet, axis string) int {
+ xlsx := f.workSheetReader(sheet)
+ axis = f.mergeCellsParser(xlsx, axis)
+ col := string(strings.Map(letterOnlyMapF, axis))
+ row, _ := strconv.Atoi(strings.Map(intOnlyMapF, axis))
+ 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)
+}
+
// GetCellFormula provides function to get formula from cell by given sheet
// index and axis in XLSX file.
func (f *File) GetCellFormula(sheet, axis string) string {
@@ -215,6 +284,94 @@ func (f *File) MergeCell(sheet, hcell, vcell string) {
}
}
+// SetCellInt provides 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, _ := strconv.Atoi(strings.Map(intOnlyMapF, axis))
+ 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)
+}
+
+// prepareCellStyle provides 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
+ }
+ }
+ }
+ return style
+}
+
+// SetCellStr provides 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, _ := strconv.Atoi(strings.Map(intOnlyMapF, axis))
+ xAxis := row - 1
+ yAxis := TitleToNumber(col)
+
+ rows := xAxis + 1
+ cell := yAxis + 1
+
+ completeRow(xlsx, rows, cell)
+ completeCol(xlsx, rows, cell)
+
+ // 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
+}
+
+// SetCellDefault provides 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, _ := strconv.Atoi(strings.Map(intOnlyMapF, axis))
+ 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 = value
+}
+
// checkCellInArea provides function to determine if a given coordinate is
// within an area.
func checkCellInArea(cell, area string) bool {
diff --git a/col.go b/col.go
index 7934ef38..00d71357 100644
--- a/col.go
+++ b/col.go
@@ -1,7 +1,9 @@
package excelize
import (
+ "bytes"
"math"
+ "strconv"
"strings"
)
@@ -243,6 +245,61 @@ func (f *File) GetColWidth(sheet, column string) float64 {
return defaultColWidthPixels
}
+// InsertCol provides function to insert a new column before given column index.
+// For example, create a new column before column C in Sheet1:
+//
+// xlsx.InsertCol("Sheet1", "C")
+//
+func (f *File) InsertCol(sheet, column string) {
+ col := TitleToNumber(strings.ToUpper(column))
+ f.adjustHelper(sheet, col, -1, 1)
+}
+
+// RemoveCol provides function to remove single column by given worksheet index
+// and column index. For example, remove column C in Sheet1:
+//
+// xlsx.RemoveCol("Sheet1", "C")
+//
+func (f *File) RemoveCol(sheet, column string) {
+ xlsx := f.workSheetReader(sheet)
+ for i, r := range xlsx.SheetData.Row {
+ for k, v := range r.C {
+ axis := v.R
+ col := string(strings.Map(letterOnlyMapF, axis))
+ if col == column {
+ xlsx.SheetData.Row[i].C = append(xlsx.SheetData.Row[i].C[:k], xlsx.SheetData.Row[i].C[k+1:]...)
+ }
+ }
+ }
+ col := TitleToNumber(strings.ToUpper(column))
+ f.adjustHelper(sheet, col, -1, -1)
+}
+
+// Completion column element tags of XML in a sheet.
+func completeCol(xlsx *xlsxWorksheet, row, cell int) {
+ if len(xlsx.SheetData.Row) < cell {
+ for i := len(xlsx.SheetData.Row); i < cell; i++ {
+ xlsx.SheetData.Row = append(xlsx.SheetData.Row, xlsxRow{
+ R: i + 1,
+ })
+ }
+ }
+ buffer := bytes.Buffer{}
+ for k, v := range xlsx.SheetData.Row {
+ if len(v.C) < cell {
+ start := len(v.C)
+ for iii := start; iii < cell; iii++ {
+ buffer.WriteString(ToAlphaString(iii))
+ buffer.WriteString(strconv.Itoa(k + 1))
+ xlsx.SheetData.Row[k].C = append(xlsx.SheetData.Row[k].C, xlsxC{
+ R: buffer.String(),
+ })
+ buffer.Reset()
+ }
+ }
+ }
+}
+
// convertColWidthToPixels provieds function to convert the width of a cell from
// user's units to pixels. Excel rounds the column width to the nearest pixel.
// If the width hasn't been set by the user we use the default value. If the
@@ -261,15 +318,3 @@ func convertColWidthToPixels(width float64) float64 {
pixels = (width*maxDigitWidth + 0.5) + padding
return math.Ceil(pixels)
}
-
-// convertRowHeightToPixels provides function to convert the height of a cell
-// from user's units to pixels. If the height hasn't been set by the user we use
-// the default value. If the row is hidden it has a value of zero.
-func convertRowHeightToPixels(height float64) float64 {
- var pixels float64
- if height == 0 {
- return pixels
- }
- pixels = math.Ceil(4.0 / 3.0 * height)
- return pixels
-}
diff --git a/excelize.go b/excelize.go
index 7314ead9..681759e8 100644
--- a/excelize.go
+++ b/excelize.go
@@ -4,13 +4,11 @@ import (
"archive/zip"
"bytes"
"encoding/xml"
- "fmt"
"io"
"io/ioutil"
"os"
"strconv"
"strings"
- "time"
)
// File define a populated XLSX file struct.
@@ -67,72 +65,6 @@ func OpenReader(r io.Reader) (*File, error) {
}, nil
}
-// SetCellValue provides function to set value of a cell. The following shows
-// the supported data types:
-//
-// int
-// int8
-// int16
-// int32
-// int64
-// float32
-// float64
-// string
-// []byte
-// time.Time
-// nil
-//
-// 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 int:
- f.SetCellInt(sheet, axis, value.(int))
- case int8:
- f.SetCellInt(sheet, axis, int(value.(int8)))
- case int16:
- f.SetCellInt(sheet, axis, int(value.(int16)))
- case int32:
- f.SetCellInt(sheet, axis, int(value.(int32)))
- case int64:
- f.SetCellInt(sheet, axis, int(value.(int64)))
- 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.Time:
- f.SetCellDefault(sheet, axis, strconv.FormatFloat(float64(timeToExcelTime(timeToUTCTime(value.(time.Time)))), 'f', -1, 32))
- f.setDefaultTimeStyle(sheet, axis)
- case nil:
- f.SetCellStr(sheet, axis, "")
- default:
- f.SetCellStr(sheet, axis, fmt.Sprintf("%v", value))
- }
-}
-
-// GetCellStyle provides function to get cell style index by given worksheet
-// name and cell coordinates.
-func (f *File) GetCellStyle(sheet, axis string) int {
- xlsx := f.workSheetReader(sheet)
- axis = f.mergeCellsParser(xlsx, axis)
- col := string(strings.Map(letterOnlyMapF, axis))
- row, _ := strconv.Atoi(strings.Map(intOnlyMapF, axis))
- 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)
-}
-
// setDefaultTimeStyle provides function to set default numbers format for
// time.Time type cell value by given worksheet name and cell coordinates.
func (f *File) setDefaultTimeStyle(sheet, axis string) {
@@ -163,151 +95,6 @@ func (f *File) workSheetReader(sheet string) *xlsxWorksheet {
return f.Sheet[name]
}
-// SetCellInt provides 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, _ := strconv.Atoi(strings.Map(intOnlyMapF, axis))
- 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)
-}
-
-// prepareCellStyle provides 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
- }
- }
- }
- return style
-}
-
-// SetCellStr provides 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, _ := strconv.Atoi(strings.Map(intOnlyMapF, axis))
- xAxis := row - 1
- yAxis := TitleToNumber(col)
-
- rows := xAxis + 1
- cell := yAxis + 1
-
- completeRow(xlsx, rows, cell)
- completeCol(xlsx, rows, cell)
-
- // 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
-}
-
-// SetCellDefault provides 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, _ := strconv.Atoi(strings.Map(intOnlyMapF, axis))
- 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 = value
-}
-
-// Completion column element tags of XML in a sheet.
-func completeCol(xlsx *xlsxWorksheet, row, cell int) {
- if len(xlsx.SheetData.Row) < cell {
- for i := len(xlsx.SheetData.Row); i < cell; i++ {
- xlsx.SheetData.Row = append(xlsx.SheetData.Row, xlsxRow{
- R: i + 1,
- })
- }
- }
- buffer := bytes.Buffer{}
- for k, v := range xlsx.SheetData.Row {
- if len(v.C) < cell {
- start := len(v.C)
- for iii := start; iii < cell; iii++ {
- buffer.WriteString(ToAlphaString(iii))
- buffer.WriteString(strconv.Itoa(k + 1))
- xlsx.SheetData.Row[k].C = append(xlsx.SheetData.Row[k].C, xlsxC{
- R: buffer.String(),
- })
- buffer.Reset()
- }
- }
- }
-}
-
-// completeRow provides function to check and fill each column element for a
-// single row and make that is continuous in a worksheet of XML by given row
-// index and axis.
-func completeRow(xlsx *xlsxWorksheet, row, cell int) {
- currentRows := len(xlsx.SheetData.Row)
- if currentRows > 1 {
- lastRow := xlsx.SheetData.Row[currentRows-1].R
- if lastRow >= row {
- row = lastRow
- }
- }
- for i := currentRows; i < row; i++ {
- xlsx.SheetData.Row = append(xlsx.SheetData.Row, xlsxRow{
- R: i + 1,
- })
- }
- buffer := bytes.Buffer{}
- for ii := currentRows; ii < row; ii++ {
- start := len(xlsx.SheetData.Row[ii].C)
- if start == 0 {
- for iii := start; iii < cell; iii++ {
- buffer.WriteString(ToAlphaString(iii))
- buffer.WriteString(strconv.Itoa(ii + 1))
- xlsx.SheetData.Row[ii].C = append(xlsx.SheetData.Row[ii].C, xlsxC{
- R: buffer.String(),
- })
- buffer.Reset()
- }
- }
- }
-}
-
// checkSheet provides function to fill each row element and make that is
// continuous in a worksheet of XML.
func checkSheet(xlsx *xlsxWorksheet) {
@@ -346,61 +133,6 @@ func replaceWorkSheetsRelationshipsNameSpace(workbookMarshal string) string {
return workbookMarshal
}
-// checkRow provides function to check and fill each column element for all rows
-// and make that is continuous in a worksheet of XML. For example:
-//
-//
-//
-//
-//
-//
-//
-//
-// in this case, we should to change it to
-//
-//
-//
-//
-//
-//
-//
-//
-//
-//
-//
-// Noteice: this method could be very slow for large spreadsheets (more than
-// 3000 rows one sheet).
-func checkRow(xlsx *xlsxWorksheet) {
- buffer := bytes.Buffer{}
- for k, v := range xlsx.SheetData.Row {
- lenCol := len(v.C)
- if lenCol < 1 {
- continue
- }
- endR := string(strings.Map(letterOnlyMapF, v.C[lenCol-1].R))
- endRow, _ := strconv.Atoi(strings.Map(intOnlyMapF, v.C[lenCol-1].R))
- endCol := TitleToNumber(endR) + 1
- if lenCol < endCol {
- oldRow := xlsx.SheetData.Row[k].C
- xlsx.SheetData.Row[k].C = xlsx.SheetData.Row[k].C[:0]
- tmp := []xlsxC{}
- for i := 0; i <= endCol; i++ {
- buffer.WriteString(ToAlphaString(i))
- buffer.WriteString(strconv.Itoa(endRow))
- tmp = append(tmp, xlsxC{
- R: buffer.String(),
- })
- buffer.Reset()
- }
- xlsx.SheetData.Row[k].C = tmp
- for _, y := range oldRow {
- colAxis := TitleToNumber(string(strings.Map(letterOnlyMapF, y.R)))
- xlsx.SheetData.Row[k].C[colAxis] = y
- }
- }
- }
-}
-
// UpdateLinkedValue fix linked values within a spreadsheet are not updating in
// Office Excel 2007 and 2010. This function will be remove value tag when met a
// cell have a linked value. Reference
@@ -439,3 +171,224 @@ func (f *File) UpdateLinkedValue() {
}
}
}
+
+// adjustHelper provides function to adjust rows and columns dimensions,
+// hyperlinks, merged cells and auto filter when inserting or deleting rows or
+// columns.
+//
+// sheet: Worksheet index that we're editing
+// column: Index number of the column we're inserting/deleting before
+// row: Index number of the row we're inserting/deleting before
+// offset: Number of rows/column to insert/delete negative values indicate deletion
+//
+// TODO: adjustPageBreaks, adjustComments, adjustDataValidations, adjustProtectedCells
+//
+func (f *File) adjustHelper(sheet string, column, row, offset int) {
+ xlsx := f.workSheetReader(sheet)
+ f.adjustRowDimensions(xlsx, row, offset)
+ f.adjustColDimensions(xlsx, column, offset)
+ f.adjustHyperlinks(sheet, column, row, offset)
+ f.adjustMergeCells(xlsx, column, row, offset)
+ f.adjustAutoFilter(xlsx, column, row, offset)
+ checkSheet(xlsx)
+ checkRow(xlsx)
+}
+
+// adjustColDimensions provides function to update column dimensions when
+// inserting or deleting rows or columns.
+func (f *File) adjustColDimensions(xlsx *xlsxWorksheet, column, offset int) {
+ for i, r := range xlsx.SheetData.Row {
+ for k, v := range r.C {
+ axis := v.R
+ col := string(strings.Map(letterOnlyMapF, axis))
+ row, _ := strconv.Atoi(strings.Map(intOnlyMapF, axis))
+ yAxis := TitleToNumber(col)
+ if yAxis >= column && column != -1 {
+ xlsx.SheetData.Row[i].C[k].R = ToAlphaString(yAxis+offset) + strconv.Itoa(row)
+ }
+ }
+ }
+}
+
+// adjustRowDimensions provides function to update row dimensions when inserting
+// or deleting rows or columns.
+func (f *File) adjustRowDimensions(xlsx *xlsxWorksheet, rowIndex, offset int) {
+ if rowIndex == -1 {
+ return
+ }
+ for i, r := range xlsx.SheetData.Row {
+ if r.R < rowIndex {
+ continue
+ }
+ xlsx.SheetData.Row[i].R += offset
+ for k, v := range xlsx.SheetData.Row[i].C {
+ axis := v.R
+ col := string(strings.Map(letterOnlyMapF, axis))
+ row, _ := strconv.Atoi(strings.Map(intOnlyMapF, axis))
+ xAxis := row + offset
+ xlsx.SheetData.Row[i].C[k].R = col + strconv.Itoa(xAxis)
+ }
+ }
+}
+
+// adjustHyperlinks provides function to update hyperlinks when inserting or
+// deleting rows or columns.
+func (f *File) adjustHyperlinks(sheet string, column, rowIndex, offset int) {
+ xlsx := f.workSheetReader(sheet)
+
+ // order is important
+ if xlsx.Hyperlinks != nil && offset < 0 {
+ for i, v := range xlsx.Hyperlinks.Hyperlink {
+ axis := v.Ref
+ col := string(strings.Map(letterOnlyMapF, axis))
+ row, _ := strconv.Atoi(strings.Map(intOnlyMapF, axis))
+ yAxis := TitleToNumber(col)
+ if row == rowIndex || yAxis == column {
+ f.deleteSheetRelationships(sheet, v.RID)
+ if len(xlsx.Hyperlinks.Hyperlink) > 1 {
+ xlsx.Hyperlinks.Hyperlink = append(xlsx.Hyperlinks.Hyperlink[:i], xlsx.Hyperlinks.Hyperlink[i+1:]...)
+ } else {
+ xlsx.Hyperlinks = nil
+ }
+ }
+ }
+ }
+
+ if xlsx.Hyperlinks != nil {
+ for i, v := range xlsx.Hyperlinks.Hyperlink {
+ axis := v.Ref
+ col := string(strings.Map(letterOnlyMapF, axis))
+ row, _ := strconv.Atoi(strings.Map(intOnlyMapF, axis))
+ xAxis := row + offset
+ yAxis := TitleToNumber(col)
+ if rowIndex != -1 && row >= rowIndex {
+ xlsx.Hyperlinks.Hyperlink[i].Ref = col + strconv.Itoa(xAxis)
+ }
+ if column != -1 && yAxis >= column {
+ xlsx.Hyperlinks.Hyperlink[i].Ref = ToAlphaString(yAxis+offset) + strconv.Itoa(row)
+ }
+ }
+ }
+}
+
+// adjustMergeCellsHelper provides function to update merged cells when inserting or
+// deleting rows or columns.
+func (f *File) adjustMergeCellsHelper(xlsx *xlsxWorksheet, column, rowIndex, offset int) {
+ if xlsx.MergeCells != nil {
+ for k, v := range xlsx.MergeCells.Cells {
+ beg := strings.Split(v.Ref, ":")[0]
+ end := strings.Split(v.Ref, ":")[1]
+
+ begcol := string(strings.Map(letterOnlyMapF, beg))
+ begrow, _ := strconv.Atoi(strings.Map(intOnlyMapF, beg))
+ begxAxis := begrow + offset
+ begyAxis := TitleToNumber(begcol)
+
+ endcol := string(strings.Map(letterOnlyMapF, end))
+ endrow, _ := strconv.Atoi(strings.Map(intOnlyMapF, end))
+ endxAxis := endrow + offset
+ endyAxis := TitleToNumber(endcol)
+
+ if rowIndex != -1 {
+ if begrow > 1 && begrow >= rowIndex {
+ beg = begcol + strconv.Itoa(begxAxis)
+ }
+ if endrow > 1 && endrow >= rowIndex {
+ end = endcol + strconv.Itoa(endxAxis)
+ }
+ }
+
+ if column != -1 {
+ if begyAxis >= column {
+ beg = ToAlphaString(begyAxis+offset) + strconv.Itoa(endrow)
+ }
+ if endyAxis >= column {
+ end = ToAlphaString(endyAxis+offset) + strconv.Itoa(endrow)
+ }
+ }
+
+ xlsx.MergeCells.Cells[k].Ref = beg + ":" + end
+ }
+ }
+}
+
+// adjustMergeCells provides function to update merged cells when inserting or
+// deleting rows or columns.
+func (f *File) adjustMergeCells(xlsx *xlsxWorksheet, column, rowIndex, offset int) {
+ f.adjustMergeCellsHelper(xlsx, column, rowIndex, offset)
+
+ if xlsx.MergeCells != nil && offset < 0 {
+ for k, v := range xlsx.MergeCells.Cells {
+ beg := strings.Split(v.Ref, ":")[0]
+ end := strings.Split(v.Ref, ":")[1]
+ if beg == end {
+ xlsx.MergeCells.Count += offset
+ if len(xlsx.MergeCells.Cells) > 1 {
+ xlsx.MergeCells.Cells = append(xlsx.MergeCells.Cells[:k], xlsx.MergeCells.Cells[k+1:]...)
+ } else {
+ xlsx.MergeCells = nil
+ }
+ }
+ }
+ }
+}
+
+// adjustAutoFilter provides function to update the auto filter when inserting
+// or deleting rows or columns.
+func (f *File) adjustAutoFilter(xlsx *xlsxWorksheet, column, rowIndex, offset int) {
+ f.adjustAutoFilterHelper(xlsx, column, rowIndex, offset)
+
+ if xlsx.AutoFilter != nil {
+ beg := strings.Split(xlsx.AutoFilter.Ref, ":")[0]
+ end := strings.Split(xlsx.AutoFilter.Ref, ":")[1]
+
+ begcol := string(strings.Map(letterOnlyMapF, beg))
+ begrow, _ := strconv.Atoi(strings.Map(intOnlyMapF, beg))
+ begxAxis := begrow + offset
+
+ endcol := string(strings.Map(letterOnlyMapF, end))
+ endrow, _ := strconv.Atoi(strings.Map(intOnlyMapF, end))
+ endxAxis := endrow + offset
+ endyAxis := TitleToNumber(endcol)
+
+ if rowIndex != -1 {
+ if begrow >= rowIndex {
+ beg = begcol + strconv.Itoa(begxAxis)
+ }
+ if endrow >= rowIndex {
+ end = endcol + strconv.Itoa(endxAxis)
+ }
+ }
+
+ if column != -1 && endyAxis >= column {
+ end = ToAlphaString(endyAxis+offset) + strconv.Itoa(endrow)
+ }
+ xlsx.AutoFilter.Ref = beg + ":" + end
+ }
+}
+
+// adjustAutoFilterHelper provides function to update the auto filter when
+// inserting or deleting rows or columns.
+func (f *File) adjustAutoFilterHelper(xlsx *xlsxWorksheet, column, rowIndex, offset int) {
+ if xlsx.AutoFilter != nil {
+ beg := strings.Split(xlsx.AutoFilter.Ref, ":")[0]
+ end := strings.Split(xlsx.AutoFilter.Ref, ":")[1]
+
+ begcol := string(strings.Map(letterOnlyMapF, beg))
+ begrow, _ := strconv.Atoi(strings.Map(intOnlyMapF, beg))
+ begyAxis := TitleToNumber(begcol)
+
+ endcol := string(strings.Map(letterOnlyMapF, end))
+ endyAxis := TitleToNumber(endcol)
+ endrow, _ := strconv.Atoi(strings.Map(intOnlyMapF, end))
+
+ if (begrow == rowIndex && offset < 0) || (column == begyAxis && column == endyAxis) {
+ xlsx.AutoFilter = nil
+ for i, r := range xlsx.SheetData.Row {
+ if begrow < r.R && r.R <= endrow {
+ xlsx.SheetData.Row[i].Hidden = false
+ }
+ }
+ }
+ }
+}
diff --git a/excelize_test.go b/excelize_test.go
index d6e6d052..23630f35 100644
--- a/excelize_test.go
+++ b/excelize_test.go
@@ -750,7 +750,88 @@ func TestAddChart(t *testing.T) {
xlsx.AddChart("SHEET2", "P1", `{"type":"radar","series":[{"name":"=Sheet1!$A$30","categories":"=Sheet1!$B$29:$D$29","values":"=Sheet1!$B$30:$D$30"},{"name":"=Sheet1!$A$31","categories":"=Sheet1!$B$29:$D$29","values":"=Sheet1!$B$31:$D$31"},{"name":"=Sheet1!$A$32","categories":"=Sheet1!$B$29:$D$29","values":"=Sheet1!$B$32:$D$32"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"top_right","show_legend_key":false},"title":{"name":"Fruit Radar Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"span"}`)
xlsx.AddChart("SHEET2", "X1", `{"type":"scatter","series":[{"name":"=Sheet1!$A$30","categories":"=Sheet1!$B$29:$D$29","values":"=Sheet1!$B$30:$D$30"},{"name":"=Sheet1!$A$31","categories":"=Sheet1!$B$29:$D$29","values":"=Sheet1!$B$31:$D$31"},{"name":"=Sheet1!$A$32","categories":"=Sheet1!$B$29:$D$29","values":"=Sheet1!$B$32:$D$32"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"bottom","show_legend_key":false},"title":{"name":"Fruit Scatter Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`)
// Save xlsx file by the given path.
- err = xlsx.SaveAs("./test/Workbook_6.xlsx")
+ err = xlsx.SaveAs("./test/Workbook_addchart.xlsx")
+ if err != nil {
+ t.Log(err)
+ }
+}
+
+func TestInsertCol(t *testing.T) {
+ xlsx := NewFile()
+ for j := 1; j <= 10; j++ {
+ for i := 0; i <= 10; i++ {
+ axis := ToAlphaString(i) + strconv.Itoa(j)
+ xlsx.SetCellStr("Sheet1", axis, axis)
+ }
+ }
+ xlsx.SetCellHyperLink("Sheet1", "A5", "https://github.com/xuri/excelize")
+ xlsx.MergeCell("sheet1", "A1", "C3")
+ err := xlsx.AutoFilter("Sheet1", "A2", "B2", `{"column":"B","expression":"x != blanks"}`)
+ t.Log(err)
+ xlsx.InsertCol("Sheet1", "A")
+ err = xlsx.SaveAs("./test/Workbook_insertcol.xlsx")
+ if err != nil {
+ t.Log(err)
+ }
+}
+
+func TestRemoveCol(t *testing.T) {
+ xlsx := NewFile()
+ for j := 1; j <= 10; j++ {
+ for i := 0; i <= 10; i++ {
+ axis := ToAlphaString(i) + strconv.Itoa(j)
+ xlsx.SetCellStr("Sheet1", axis, axis)
+ }
+ }
+ xlsx.SetCellHyperLink("Sheet1", "A5", "https://github.com/xuri/excelize")
+ xlsx.SetCellHyperLink("Sheet1", "C5", "https://github.com")
+ xlsx.MergeCell("sheet1", "A1", "B1")
+ xlsx.MergeCell("sheet1", "A2", "B2")
+ xlsx.RemoveCol("Sheet1", "A")
+ xlsx.RemoveCol("Sheet1", "A")
+ err := xlsx.SaveAs("./test/Workbook_removecol.xlsx")
+ if err != nil {
+ t.Log(err)
+ }
+}
+
+func TestInsertRow(t *testing.T) {
+ xlsx := NewFile()
+ for j := 1; j <= 10; j++ {
+ for i := 0; i <= 10; i++ {
+ axis := ToAlphaString(i) + strconv.Itoa(j)
+ xlsx.SetCellStr("Sheet1", axis, axis)
+ }
+ }
+ xlsx.SetCellHyperLink("Sheet1", "A5", "https://github.com/xuri/excelize")
+ xlsx.InsertRow("Sheet1", -1)
+ xlsx.InsertRow("Sheet1", 4)
+ err := xlsx.SaveAs("./test/Workbook_insertrow.xlsx")
+ if err != nil {
+ t.Log(err)
+ }
+}
+
+func TestRemoveRow(t *testing.T) {
+ xlsx := NewFile()
+ for j := 1; j <= 10; j++ {
+ for i := 0; i <= 10; i++ {
+ axis := ToAlphaString(i) + strconv.Itoa(j)
+ xlsx.SetCellStr("Sheet1", axis, axis)
+ }
+ }
+ xlsx.SetCellHyperLink("Sheet1", "A5", "https://github.com/xuri/excelize")
+ xlsx.RemoveRow("Sheet1", -1)
+ xlsx.RemoveRow("Sheet1", 4)
+ xlsx.MergeCell("sheet1", "B3", "B5")
+ xlsx.RemoveRow("Sheet1", 2)
+ xlsx.RemoveRow("Sheet1", 4)
+ err := xlsx.AutoFilter("Sheet1", "A2", "A2", `{"column":"A","expression":"x != blanks"}`)
+ t.Log(err)
+ xlsx.RemoveRow("Sheet1", 0)
+ xlsx.RemoveRow("Sheet1", 1)
+ xlsx.RemoveRow("Sheet1", 0)
+ err = xlsx.SaveAs("./test/Workbook_removerow.xlsx")
if err != nil {
t.Log(err)
}
diff --git a/picture.go b/picture.go
index 3ae309dc..f2216162 100644
--- a/picture.go
+++ b/picture.go
@@ -127,6 +127,23 @@ func (f *File) addSheetRelationships(sheet, relType, target, targetMode string)
return rID
}
+// deleteSheetRelationships provides function to delete relationships in
+// xl/worksheets/_rels/sheet%d.xml.rels by given sheet name and relationship
+// index.
+func (f *File) deleteSheetRelationships(sheet, rID string) {
+ var rels = "xl/worksheets/_rels/" + strings.ToLower(sheet) + ".xml.rels"
+ var sheetRels xlsxWorkbookRels
+ xml.Unmarshal([]byte(f.readXML(rels)), &sheetRels)
+ for k, v := range sheetRels.Relationships {
+ if v.ID != rID {
+ continue
+ }
+ sheetRels.Relationships = append(sheetRels.Relationships[:k], sheetRels.Relationships[k+1:]...)
+ }
+ output, _ := xml.Marshal(sheetRels)
+ f.saveFileList(rels, string(output))
+}
+
// addSheetLegacyDrawing provides function to add legacy drawing element to
// xl/worksheets/sheet%d.xml by given sheet name and relationship index.
func (f *File) addSheetLegacyDrawing(sheet string, rID int) {
diff --git a/rows.go b/rows.go
index 227a7d5c..c45b49fa 100644
--- a/rows.go
+++ b/rows.go
@@ -1,7 +1,9 @@
package excelize
import (
+ "bytes"
"encoding/xml"
+ "math"
"strconv"
"strings"
)
@@ -209,3 +211,136 @@ func (f *File) GetRowVisible(sheet string, rowIndex int) bool {
completeRow(xlsx, rows, cells)
return !xlsx.SheetData.Row[rowIndex].Hidden
}
+
+// RemoveRow provides function to remove single row by given worksheet index and
+// row index. For example, remove row 3 in Sheet1:
+//
+// xlsx.RemoveRow("Sheet1", 2)
+//
+func (f *File) RemoveRow(sheet string, row int) {
+ if row < 0 {
+ return
+ }
+ xlsx := f.workSheetReader(sheet)
+ row++
+ for i, r := range xlsx.SheetData.Row {
+ if r.R != row {
+ continue
+ }
+ xlsx.SheetData.Row = append(xlsx.SheetData.Row[:i], xlsx.SheetData.Row[i+1:]...)
+ f.adjustHelper(sheet, -1, row, -1)
+ return
+ }
+}
+
+// InsertRow provides function to insert a new row before given row index. For
+// example, create a new row before row 3 in Sheet1:
+//
+// xlsx.InsertRow("Sheet1", 2)
+//
+func (f *File) InsertRow(sheet string, row int) {
+ if row < 0 {
+ return
+ }
+ row++
+ f.adjustHelper(sheet, -1, row, 1)
+}
+
+// checkRow provides function to check and fill each column element for all rows
+// and make that is continuous in a worksheet of XML. For example:
+//
+//
+//
+//
+//
+//
+//
+//
+// in this case, we should to change it to
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+// Noteice: this method could be very slow for large spreadsheets (more than
+// 3000 rows one sheet).
+func checkRow(xlsx *xlsxWorksheet) {
+ buffer := bytes.Buffer{}
+ for k, v := range xlsx.SheetData.Row {
+ lenCol := len(v.C)
+ if lenCol < 1 {
+ continue
+ }
+ endR := string(strings.Map(letterOnlyMapF, v.C[lenCol-1].R))
+ endRow, _ := strconv.Atoi(strings.Map(intOnlyMapF, v.C[lenCol-1].R))
+ endCol := TitleToNumber(endR) + 1
+ if lenCol < endCol {
+ oldRow := xlsx.SheetData.Row[k].C
+ xlsx.SheetData.Row[k].C = xlsx.SheetData.Row[k].C[:0]
+ tmp := []xlsxC{}
+ for i := 0; i <= endCol; i++ {
+ buffer.WriteString(ToAlphaString(i))
+ buffer.WriteString(strconv.Itoa(endRow))
+ tmp = append(tmp, xlsxC{
+ R: buffer.String(),
+ })
+ buffer.Reset()
+ }
+ xlsx.SheetData.Row[k].C = tmp
+ for _, y := range oldRow {
+ colAxis := TitleToNumber(string(strings.Map(letterOnlyMapF, y.R)))
+ xlsx.SheetData.Row[k].C[colAxis] = y
+ }
+ }
+ }
+}
+
+// completeRow provides function to check and fill each column element for a
+// single row and make that is continuous in a worksheet of XML by given row
+// index and axis.
+func completeRow(xlsx *xlsxWorksheet, row, cell int) {
+ currentRows := len(xlsx.SheetData.Row)
+ if currentRows > 1 {
+ lastRow := xlsx.SheetData.Row[currentRows-1].R
+ if lastRow >= row {
+ row = lastRow
+ }
+ }
+ for i := currentRows; i < row; i++ {
+ xlsx.SheetData.Row = append(xlsx.SheetData.Row, xlsxRow{
+ R: i + 1,
+ })
+ }
+ buffer := bytes.Buffer{}
+ for ii := currentRows; ii < row; ii++ {
+ start := len(xlsx.SheetData.Row[ii].C)
+ if start == 0 {
+ for iii := start; iii < cell; iii++ {
+ buffer.WriteString(ToAlphaString(iii))
+ buffer.WriteString(strconv.Itoa(ii + 1))
+ xlsx.SheetData.Row[ii].C = append(xlsx.SheetData.Row[ii].C, xlsxC{
+ R: buffer.String(),
+ })
+ buffer.Reset()
+ }
+ }
+ }
+}
+
+// convertRowHeightToPixels provides function to convert the height of a cell
+// from user's units to pixels. If the height hasn't been set by the user we use
+// the default value. If the row is hidden it has a value of zero.
+func convertRowHeightToPixels(height float64) float64 {
+ var pixels float64
+ if height == 0 {
+ return pixels
+ }
+ pixels = math.Ceil(4.0 / 3.0 * height)
+ return pixels
+}