瀏覽代碼

- Init insert/remove column/row support. Relate issue #77 and #82;
- Readme and go test updated

Ri Xu 8 年之前
父節點
當前提交
6aa59a1af2
共有 7 個文件被更改,包括 671 次插入283 次删除
  1. 2 2
      README.md
  2. 157 0
      cell.go
  3. 57 12
      col.go
  4. 221 268
      excelize.go
  5. 82 1
      excelize_test.go
  6. 17 0
      picture.go
  7. 135 0
      rows.go

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

+ 157 - 0
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 {

+ 57 - 12
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
-}

+ 221 - 268
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:
-//
-//    <row r="15" spans="1:22" x14ac:dyDescent="0.2">
-//        <c r="A15" s="2" />
-//        <c r="B15" s="2" />
-//        <c r="F15" s="1" />
-//        <c r="G15" s="1" />
-//    </row>
-//
-// in this case, we should to change it to
-//
-//    <row r="15" spans="1:22" x14ac:dyDescent="0.2">
-//        <c r="A15" s="2" />
-//        <c r="B15" s="2" />
-//        <c r="C15" s="2" />
-//        <c r="D15" s="2" />
-//        <c r="E15" s="2" />
-//        <c r="F15" s="1" />
-//        <c r="G15" s="1" />
-//    </row>
-//
-// 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
+				}
+			}
+		}
+	}
+}

+ 82 - 1
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)
 	}

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

+ 135 - 0
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:
+//
+//    <row r="15" spans="1:22" x14ac:dyDescent="0.2">
+//        <c r="A15" s="2" />
+//        <c r="B15" s="2" />
+//        <c r="F15" s="1" />
+//        <c r="G15" s="1" />
+//    </row>
+//
+// in this case, we should to change it to
+//
+//    <row r="15" spans="1:22" x14ac:dyDescent="0.2">
+//        <c r="A15" s="2" />
+//        <c r="B15" s="2" />
+//        <c r="C15" s="2" />
+//        <c r="D15" s="2" />
+//        <c r="E15" s="2" />
+//        <c r="F15" s="1" />
+//        <c r="G15" s="1" />
+//    </row>
+//
+// 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
+}