forked from p30928647/excelize
Huge refactorig for consistent col/row numbering (#356)
* Huge refactorig for consistent col/row numbering Started from simply changing ToALphaString()/TitleToNumber() logic and related fixes. But have to go deeper, do fixes, after do related fixes and again and again. Major improvements: 1. Tests made stronger again (But still be weak). 2. "Empty" returns for incorrect input replaces with panic. 3. Check for correct col/row/cell naming & addressing by default. 4. Removed huge amount of duplicated code. 5. Removed ToALphaString(), TitleToNumber() and it helpers functions at all, and replaced with SplitCellName(), JoinCellName(), ColumnNameToNumber(), ColumnNumberToName(), CellNameToCoordinates(), CoordinatesToCellName(). 6. Minor fixes for internal variable naming for code readability (ex. col, row for input params, colIdx, rowIdx for slice indexes etc). * Formatting fixes
This commit is contained in:
parent
092f16c744
commit
dc01264562
|
@ -1 +1,2 @@
|
|||
~$*.xlsx
|
||||
test/Test*.xlsx
|
||||
|
|
|
@ -0,0 +1,229 @@
|
|||
package excelize
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type adjustDirection bool
|
||||
|
||||
const (
|
||||
columns adjustDirection = false
|
||||
rows adjustDirection = true
|
||||
)
|
||||
|
||||
// adjustHelper provides a function to adjust rows and columns dimensions,
|
||||
// hyperlinks, merged cells and auto filter when inserting or deleting rows or
|
||||
// columns.
|
||||
//
|
||||
// sheet: Worksheet name 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: adjustCalcChain, adjustPageBreaks, adjustComments,
|
||||
// adjustDataValidations, adjustProtectedCells
|
||||
//
|
||||
func (f *File) adjustHelper(sheet string, dir adjustDirection, num, offset int) {
|
||||
xlsx := f.workSheetReader(sheet)
|
||||
|
||||
if dir == rows {
|
||||
f.adjustRowDimensions(xlsx, num, offset)
|
||||
} else {
|
||||
f.adjustColDimensions(xlsx, num, offset)
|
||||
}
|
||||
f.adjustHyperlinks(xlsx, sheet, dir, num, offset)
|
||||
f.adjustMergeCells(xlsx, dir, num, offset)
|
||||
f.adjustAutoFilter(xlsx, dir, num, offset)
|
||||
|
||||
checkSheet(xlsx)
|
||||
checkRow(xlsx)
|
||||
}
|
||||
|
||||
// adjustColDimensions provides a function to update column dimensions when
|
||||
// inserting or deleting rows or columns.
|
||||
func (f *File) adjustColDimensions(xlsx *xlsxWorksheet, col, offset int) {
|
||||
for rowIdx := range xlsx.SheetData.Row {
|
||||
for colIdx, v := range xlsx.SheetData.Row[rowIdx].C {
|
||||
cellCol, cellRow, _ := CellNameToCoordinates(v.R)
|
||||
if col <= cellCol {
|
||||
if newCol := cellCol + offset; newCol > 0 {
|
||||
xlsx.SheetData.Row[rowIdx].C[colIdx].R, _ = CoordinatesToCellName(newCol, cellRow)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// adjustRowDimensions provides a function to update row dimensions when
|
||||
// inserting or deleting rows or columns.
|
||||
func (f *File) adjustRowDimensions(xlsx *xlsxWorksheet, row, offset int) {
|
||||
for i, r := range xlsx.SheetData.Row {
|
||||
if newRow := r.R + offset; r.R >= row && newRow > 0 {
|
||||
f.ajustSingleRowDimensions(&xlsx.SheetData.Row[i], newRow)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ajustSingleRowDimensions provides a function to ajust single row dimensions.
|
||||
func (f *File) ajustSingleRowDimensions(r *xlsxRow, num int) {
|
||||
r.R = num
|
||||
for i, col := range r.C {
|
||||
colName, _, _ := SplitCellName(col.R)
|
||||
r.C[i].R, _ = JoinCellName(colName, num)
|
||||
}
|
||||
}
|
||||
|
||||
// adjustHyperlinks provides a function to update hyperlinks when inserting or
|
||||
// deleting rows or columns.
|
||||
func (f *File) adjustHyperlinks(xlsx *xlsxWorksheet, sheet string, dir adjustDirection, num, offset int) {
|
||||
// short path
|
||||
if xlsx.Hyperlinks == nil || len(xlsx.Hyperlinks.Hyperlink) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// order is important
|
||||
if offset < 0 {
|
||||
for rowIdx, linkData := range xlsx.Hyperlinks.Hyperlink {
|
||||
colNum, rowNum, _ := CellNameToCoordinates(linkData.Ref)
|
||||
|
||||
if (dir == rows && num == rowNum) || (dir == columns && num == colNum) {
|
||||
f.deleteSheetRelationships(sheet, linkData.RID)
|
||||
if len(xlsx.Hyperlinks.Hyperlink) > 1 {
|
||||
xlsx.Hyperlinks.Hyperlink = append(xlsx.Hyperlinks.Hyperlink[:rowIdx],
|
||||
xlsx.Hyperlinks.Hyperlink[rowIdx+1:]...)
|
||||
} else {
|
||||
xlsx.Hyperlinks = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if xlsx.Hyperlinks == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for i := range xlsx.Hyperlinks.Hyperlink {
|
||||
link := &xlsx.Hyperlinks.Hyperlink[i] // get reference
|
||||
colNum, rowNum, _ := CellNameToCoordinates(link.Ref)
|
||||
|
||||
if dir == rows {
|
||||
if rowNum >= num {
|
||||
link.Ref, _ = CoordinatesToCellName(colNum, rowNum+offset)
|
||||
}
|
||||
} else {
|
||||
if colNum >= num {
|
||||
link.Ref, _ = CoordinatesToCellName(colNum+offset, rowNum)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// adjustAutoFilter provides a function to update the auto filter when
|
||||
// inserting or deleting rows or columns.
|
||||
func (f *File) adjustAutoFilter(xlsx *xlsxWorksheet, dir adjustDirection, num, offset int) {
|
||||
if xlsx.AutoFilter == nil {
|
||||
return
|
||||
}
|
||||
|
||||
rng := strings.Split(xlsx.AutoFilter.Ref, ":")
|
||||
firstCell := rng[0]
|
||||
lastCell := rng[1]
|
||||
|
||||
firstCol, firstRow, err := CellNameToCoordinates(firstCell)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
lastCol, lastRow, err := CellNameToCoordinates(lastCell)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if (dir == rows && firstRow == num && offset < 0) || (dir == columns && firstCol == num && lastCol == num) {
|
||||
xlsx.AutoFilter = nil
|
||||
for rowIdx := range xlsx.SheetData.Row {
|
||||
rowData := &xlsx.SheetData.Row[rowIdx]
|
||||
if rowData.R > firstRow && rowData.R <= lastRow {
|
||||
rowData.Hidden = false
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if dir == rows {
|
||||
if firstRow >= num {
|
||||
firstCell, _ = CoordinatesToCellName(firstCol, firstRow+offset)
|
||||
}
|
||||
if lastRow >= num {
|
||||
lastCell, _ = CoordinatesToCellName(lastCol, lastRow+offset)
|
||||
}
|
||||
} else {
|
||||
if lastCol >= num {
|
||||
lastCell, _ = CoordinatesToCellName(lastCol+offset, lastRow)
|
||||
}
|
||||
}
|
||||
|
||||
xlsx.AutoFilter.Ref = firstCell + ":" + lastCell
|
||||
}
|
||||
|
||||
// adjustMergeCells provides a function to update merged cells when inserting
|
||||
// or deleting rows or columns.
|
||||
func (f *File) adjustMergeCells(xlsx *xlsxWorksheet, dir adjustDirection, num, offset int) {
|
||||
if xlsx.MergeCells == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for i, areaData := range xlsx.MergeCells.Cells {
|
||||
rng := strings.Split(areaData.Ref, ":")
|
||||
firstCell := rng[0]
|
||||
lastCell := rng[1]
|
||||
|
||||
firstCol, firstRow, err := CellNameToCoordinates(firstCell)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
lastCol, lastRow, err := CellNameToCoordinates(lastCell)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
adjust := func(v int) int {
|
||||
if v >= num {
|
||||
v += offset
|
||||
if v < 1 {
|
||||
return 1
|
||||
}
|
||||
return v
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
if dir == rows {
|
||||
firstRow = adjust(firstRow)
|
||||
lastRow = adjust(lastRow)
|
||||
} else {
|
||||
firstCol = adjust(firstCol)
|
||||
lastCol = adjust(lastCol)
|
||||
}
|
||||
|
||||
if firstCol == lastCol && firstRow == lastRow {
|
||||
if len(xlsx.MergeCells.Cells) > 1 {
|
||||
xlsx.MergeCells.Cells = append(xlsx.MergeCells.Cells[:i], xlsx.MergeCells.Cells[i+1:]...)
|
||||
xlsx.MergeCells.Count = len(xlsx.MergeCells.Cells)
|
||||
} else {
|
||||
xlsx.MergeCells = nil
|
||||
}
|
||||
}
|
||||
|
||||
if firstCell, err = CoordinatesToCellName(firstCol, firstRow); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if lastCell, err = CoordinatesToCellName(lastCol, lastRow); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
areaData.Ref = firstCell + ":" + lastCell
|
||||
}
|
||||
}
|
840
cell.go
840
cell.go
|
@ -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,118 +69,355 @@ 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) {
|
||||
switch v := value.(type) {
|
||||
case int:
|
||||
f.SetCellInt(sheet, axis, v)
|
||||
case int8:
|
||||
f.SetCellInt(sheet, axis, int(v))
|
||||
case int16:
|
||||
f.SetCellInt(sheet, axis, int(v))
|
||||
case int32:
|
||||
f.SetCellInt(sheet, axis, int(v))
|
||||
case int64:
|
||||
f.SetCellInt(sheet, axis, int(v))
|
||||
case uint:
|
||||
f.SetCellInt(sheet, axis, int(v))
|
||||
case uint8:
|
||||
f.SetCellInt(sheet, axis, int(v))
|
||||
case uint16:
|
||||
f.SetCellInt(sheet, axis, int(v))
|
||||
case uint32:
|
||||
f.SetCellInt(sheet, axis, int(v))
|
||||
case uint64:
|
||||
f.SetCellInt(sheet, axis, int(v))
|
||||
case float32:
|
||||
f.SetCellDefault(sheet, axis, strconv.FormatFloat(float64(value.(float32)), 'f', -1, 32))
|
||||
f.SetCellDefault(sheet, axis, strconv.FormatFloat(float64(v), 'f', -1, 32))
|
||||
case float64:
|
||||
f.SetCellDefault(sheet, axis, strconv.FormatFloat(float64(value.(float64)), 'f', -1, 64))
|
||||
f.SetCellDefault(sheet, axis, strconv.FormatFloat(v, 'f', -1, 64))
|
||||
case string:
|
||||
f.SetCellStr(sheet, axis, t)
|
||||
f.SetCellStr(sheet, axis, v)
|
||||
case []byte:
|
||||
f.SetCellStr(sheet, axis, string(t))
|
||||
f.SetCellStr(sheet, axis, string(v))
|
||||
case time.Duration:
|
||||
f.SetCellDefault(sheet, axis, strconv.FormatFloat(float64(value.(time.Duration).Seconds()/86400), 'f', -1, 32))
|
||||
f.SetCellDefault(sheet, axis, strconv.FormatFloat(v.Seconds()/86400.0, '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))
|
||||
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, "")
|
||||
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) {
|
||||
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 uint:
|
||||
f.SetCellInt(sheet, axis, int(value.(uint)))
|
||||
case uint8:
|
||||
f.SetCellInt(sheet, axis, int(value.(uint8)))
|
||||
case uint16:
|
||||
f.SetCellInt(sheet, axis, int(value.(uint16)))
|
||||
case uint32:
|
||||
f.SetCellInt(sheet, axis, int(value.(uint32)))
|
||||
case uint64:
|
||||
f.SetCellInt(sheet, axis, int(value.(uint64)))
|
||||
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) SetCellInt(sheet, axis string, value int) {
|
||||
xlsx := f.workSheetReader(sheet)
|
||||
cellData, col, _ := f.prepareCell(xlsx, sheet, axis)
|
||||
cellData.S = f.prepareCellStyle(xlsx, col, cellData.S)
|
||||
cellData.T = ""
|
||||
cellData.V = strconv.Itoa(value)
|
||||
}
|
||||
|
||||
// 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)
|
||||
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)
|
||||
|
||||
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"
|
||||
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)
|
||||
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",
|
||||
}
|
||||
}
|
||||
|
||||
cellData.S = f.prepareCellStyle(xlsx, col, cellData.S)
|
||||
cellData.T = "str"
|
||||
cellData.V = value
|
||||
}
|
||||
|
||||
// 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)
|
||||
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 {
|
||||
return f.getCellStringFunc(sheet, axis, func(x *xlsxWorksheet, c *xlsxC) (string, bool) {
|
||||
if c.F == nil {
|
||||
return "", false
|
||||
}
|
||||
if c.F.T == STCellFormulaTypeShared {
|
||||
return getSharedForumula(x, c.F.Si), true
|
||||
}
|
||||
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)
|
||||
cellData, _, _ := f.prepareCell(xlsx, sheet, axis)
|
||||
|
||||
if formula == "" {
|
||||
cellData.F = nil
|
||||
f.deleteCalcChain(axis)
|
||||
return
|
||||
}
|
||||
|
||||
if cellData.F != nil {
|
||||
cellData.F.Content = formula
|
||||
} else {
|
||||
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)
|
||||
row, err := strconv.Atoi(strings.Map(intOnlyMapF, 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
|
||||
}
|
||||
}
|
||||
}
|
||||
return false, ""
|
||||
}
|
||||
|
||||
// SetCellHyperLink provides a function to set cell hyperlink by given
|
||||
// worksheet name and link URL address. LinkType defines two types of
|
||||
// hyperlink "External" for web site or "Location" for moving to one of cell
|
||||
// in this workbook. The below is example for external link.
|
||||
//
|
||||
// xlsx.SetCellHyperLink("Sheet1", "A3", "https://github.com/360EntSecGroup-Skylar/excelize", "External")
|
||||
// // Set underline and font color style for the cell.
|
||||
// style, _ := xlsx.NewStyle(`{"font":{"color":"#1265BE","underline":"single"}}`)
|
||||
// xlsx.SetCellStyle("Sheet1", "A3", "A3", style)
|
||||
//
|
||||
// A this is another example for "Location":
|
||||
//
|
||||
// xlsx.SetCellHyperLink("Sheet1", "A3", "Sheet1!A40", "Location")
|
||||
//
|
||||
func (f *File) SetCellHyperLink(sheet, axis, link, linkType 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)
|
||||
|
||||
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)
|
||||
}
|
||||
xlsx.Hyperlinks.Hyperlink = append(xlsx.Hyperlinks.Hyperlink, linkData)
|
||||
}
|
||||
|
||||
// MergeCell provides a function to merge cells by given coordinate area and
|
||||
// sheet name. For example create a merged cell of D3:E9 on Sheet1:
|
||||
//
|
||||
// xlsx.MergeCell("Sheet1", "D3", "E9")
|
||||
//
|
||||
// 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) {
|
||||
hcol, hrow, err := CellNameToCoordinates(hcell)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
vcol, vrow, err := CellNameToCoordinates(vcell)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if hcol == vcol && hrow == vrow {
|
||||
return
|
||||
}
|
||||
|
||||
if vcol < hcol {
|
||||
hcol, vcol = vcol, hcol
|
||||
}
|
||||
|
||||
if vrow < hrow {
|
||||
hrow, vrow = vrow, hrow
|
||||
}
|
||||
|
||||
hcell, _ = CoordinatesToCellName(hcol, hrow)
|
||||
vcell, _ = CoordinatesToCellName(vcol, vrow)
|
||||
|
||||
xlsx := f.workSheetReader(sheet)
|
||||
if xlsx.MergeCells != nil {
|
||||
ref := hcell + ":" + vcell
|
||||
cells := make([]*xlsxMergeCell, 0, len(xlsx.MergeCells.Cells))
|
||||
|
||||
// Delete the merged cells of the overlapping area.
|
||||
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)
|
||||
}
|
||||
}
|
||||
cells = append(xlsx.MergeCells.Cells, &xlsxMergeCell{Ref: ref})
|
||||
xlsx.MergeCells.Cells = cells
|
||||
} else {
|
||||
xlsx.MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: hcell + ":" + vcell}}}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
panic(err) // Fail fast to avoid future side effects!
|
||||
}
|
||||
|
||||
// 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()
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
panic(err) // Fail fast and prevent future side effects
|
||||
}
|
||||
|
||||
prepareSheetXML(xlsx, col, row)
|
||||
|
||||
return &xlsx.SheetData.Row[row-1].C[col-1], col, row
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
_, row, err := CellNameToCoordinates(axis)
|
||||
if err != nil {
|
||||
panic(err) // Fail fast to avoid future side effects!
|
||||
}
|
||||
|
||||
lastRowNum := 0
|
||||
if l := len(xlsx.SheetData.Row); l > 0 {
|
||||
lastRowNum = xlsx.SheetData.Row[l-1].R
|
||||
}
|
||||
|
||||
// keep in mind: row starts from 1
|
||||
if row > lastRowNum {
|
||||
return ""
|
||||
}
|
||||
xAxis := row - 1
|
||||
rows := len(xlsx.SheetData.Row)
|
||||
if rows > 1 {
|
||||
lastRow := xlsx.SheetData.Row[rows-1].R
|
||||
if lastRow >= rows {
|
||||
rows = lastRow
|
||||
|
||||
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 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())
|
||||
if val, ok := fn(xlsx, colData); ok {
|
||||
return val
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
|
@ -198,64 +436,50 @@ func (f *File) formattedValue(s int, v string) string {
|
|||
return v
|
||||
}
|
||||
|
||||
// GetCellStyle provides a 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, err := strconv.Atoi(strings.Map(intOnlyMapF, axis))
|
||||
if err != nil {
|
||||
return 0
|
||||
// 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
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
return style
|
||||
}
|
||||
|
||||
// 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))
|
||||
// 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 {
|
||||
col, row, err := CellNameToCoordinates(cell)
|
||||
if err != nil {
|
||||
return ""
|
||||
panic(err)
|
||||
}
|
||||
xAxis := row - 1
|
||||
rows := len(xlsx.SheetData.Row)
|
||||
if rows > 1 {
|
||||
lastRow := xlsx.SheetData.Row[rows-1].R
|
||||
if lastRow >= rows {
|
||||
rows = lastRow
|
||||
|
||||
rng := strings.Split(area, ":")
|
||||
if len(rng) != 2 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
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 ""
|
||||
|
||||
firstCol, firtsRow, _ := CellNameToCoordinates(rng[0])
|
||||
lastCol, lastRow, _ := CellNameToCoordinates(rng[1])
|
||||
|
||||
return col >= firstCol && col <= lastCol && row >= firtsRow && row <= lastRow
|
||||
}
|
||||
|
||||
// getSharedForumula find a cell contains the same formula as another cell,
|
||||
|
@ -267,338 +491,12 @@ func (f *File) GetCellFormula(sheet, axis string) string {
|
|||
// 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
|
||||
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 ""
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
if formula == "" {
|
||||
xlsx.SheetData.Row[xAxis].C[yAxis].F = nil
|
||||
f.deleteCalcChain(axis)
|
||||
return
|
||||
}
|
||||
if xlsx.SheetData.Row[xAxis].C[yAxis].F != nil {
|
||||
xlsx.SheetData.Row[xAxis].C[yAxis].F.Content = formula
|
||||
} else {
|
||||
f := xlsxF{
|
||||
Content: formula,
|
||||
}
|
||||
xlsx.SheetData.Row[xAxis].C[yAxis].F = &f
|
||||
}
|
||||
}
|
||||
|
||||
// SetCellHyperLink provides a function to set cell hyperlink by given
|
||||
// worksheet name and link URL address. LinkType defines two types of
|
||||
// hyperlink "External" for web site or "Location" for moving to one of cell
|
||||
// in this workbook. The below is example for external link.
|
||||
//
|
||||
// xlsx.SetCellHyperLink("Sheet1", "A3", "https://github.com/360EntSecGroup-Skylar/excelize", "External")
|
||||
// // Set underline and font color style for the cell.
|
||||
// style, _ := xlsx.NewStyle(`{"font":{"color":"#1265BE","underline":"single"}}`)
|
||||
// xlsx.SetCellStyle("Sheet1", "A3", "A3", style)
|
||||
//
|
||||
// A this is another example for "Location":
|
||||
//
|
||||
// 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},
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
return link, target
|
||||
}
|
||||
|
||||
// MergeCell provides a function to merge cells by given coordinate area and
|
||||
// sheet name. For example create a merged cell of D3:E9 on Sheet1:
|
||||
//
|
||||
// xlsx.MergeCell("Sheet1", "D3", "E9")
|
||||
//
|
||||
// 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
|
||||
}
|
||||
|
||||
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 := string(strings.Map(letterOnlyMapF, vcell))
|
||||
vrow, _ := strconv.Atoi(strings.Map(intOnlyMapF, vcell))
|
||||
vyAxis := vrow - 1
|
||||
vxAxis := TitleToNumber(vcol)
|
||||
|
||||
if vxAxis < hxAxis {
|
||||
hcell, vcell = vcell, hcell
|
||||
vxAxis, hxAxis = hxAxis, vxAxis
|
||||
}
|
||||
|
||||
if vyAxis < hyAxis {
|
||||
hcell, vcell = vcell, hcell
|
||||
vyAxis, hyAxis = hyAxis, vyAxis
|
||||
}
|
||||
|
||||
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)
|
||||
// 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:]...)
|
||||
}
|
||||
}
|
||||
xlsx.MergeCells.Cells = append(xlsx.MergeCells.Cells, &mergeCell)
|
||||
} 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
|
||||
}
|
||||
}
|
||||
|
||||
// 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))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
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 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
|
||||
}
|
||||
}
|
||||
}
|
||||
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))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
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 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
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
v = v.Elem()
|
||||
if v.Kind() != reflect.Slice {
|
||||
return
|
||||
}
|
||||
|
||||
xAxis := row - 1
|
||||
yAxis := TitleToNumber(col)
|
||||
|
||||
rows := xAxis + 1
|
||||
cell := yAxis + 1
|
||||
|
||||
completeRow(xlsx, rows, cell)
|
||||
completeCol(xlsx, rows, cell)
|
||||
|
||||
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++
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
ref := strings.Split(area, ":")
|
||||
if len(ref) < 2 {
|
||||
return false
|
||||
}
|
||||
|
||||
from := ref[0]
|
||||
to := ref[1]
|
||||
|
||||
col, row := getCellColRow(cell)
|
||||
fromCol, fromRow := getCellColRow(from)
|
||||
toCol, toRow := getCellColRow(to)
|
||||
|
||||
return axisLowerOrEqualThan(fromCol, col) && axisLowerOrEqualThan(col, toCol) && axisLowerOrEqualThan(fromRow, row) && axisLowerOrEqualThan(row, toRow)
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ import (
|
|||
func TestCheckCellInArea(t *testing.T) {
|
||||
expectedTrueCellInAreaList := [][2]string{
|
||||
{"c2", "A1:AAZ32"},
|
||||
{"AA0", "Z0:AB1"},
|
||||
{"B9", "A1:B9"},
|
||||
{"C2", "C2:C2"},
|
||||
}
|
||||
|
@ -18,7 +17,7 @@ func TestCheckCellInArea(t *testing.T) {
|
|||
cell := expectedTrueCellInArea[0]
|
||||
area := expectedTrueCellInArea[1]
|
||||
|
||||
assert.True(t, checkCellInArea(cell, area),
|
||||
assert.Truef(t, checkCellInArea(cell, area),
|
||||
"Expected cell %v to be in area %v, got false\n", cell, area)
|
||||
}
|
||||
|
||||
|
@ -32,7 +31,11 @@ func TestCheckCellInArea(t *testing.T) {
|
|||
cell := expectedFalseCellInArea[0]
|
||||
area := expectedFalseCellInArea[1]
|
||||
|
||||
assert.False(t, checkCellInArea(cell, area),
|
||||
assert.Falsef(t, checkCellInArea(cell, area),
|
||||
"Expected cell %v not to be inside of area %v, but got true\n", cell, area)
|
||||
}
|
||||
|
||||
assert.Panics(t, func() {
|
||||
checkCellInArea("AA0", "Z0:AB1")
|
||||
})
|
||||
}
|
||||
|
|
12
chart.go
12
chart.go
|
@ -1240,14 +1240,14 @@ func (f *File) drawingParser(path string) (*xlsxWsDr, int) {
|
|||
// addDrawingChart provides a function to add chart graphic frame by given
|
||||
// sheet, drawingXML, cell, width, height, relationship index and format sets.
|
||||
func (f *File) addDrawingChart(sheet, drawingXML, cell string, width, height, rID int, formatSet *formatPicture) {
|
||||
cell = strings.ToUpper(cell)
|
||||
fromCol := string(strings.Map(letterOnlyMapF, cell))
|
||||
fromRow, _ := strconv.Atoi(strings.Map(intOnlyMapF, cell))
|
||||
row := fromRow - 1
|
||||
col := TitleToNumber(fromCol)
|
||||
col, row := MustCellNameToCoordinates(cell)
|
||||
colIdx := col - 1
|
||||
rowIdx := row - 1
|
||||
|
||||
width = int(float64(width) * formatSet.XScale)
|
||||
height = int(float64(height) * formatSet.YScale)
|
||||
colStart, rowStart, _, _, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, col, row, formatSet.OffsetX, formatSet.OffsetY, width, height)
|
||||
colStart, rowStart, _, _, colEnd, rowEnd, x2, y2 :=
|
||||
f.positionObjectPixels(sheet, colIdx, rowIdx, formatSet.OffsetX, formatSet.OffsetY, width, height)
|
||||
content, cNvPrID := f.drawingParser(drawingXML)
|
||||
twoCellAnchor := xdrCellAnchor{}
|
||||
twoCellAnchor.EditAs = formatSet.Positioning
|
||||
|
|
|
@ -9,19 +9,46 @@ import (
|
|||
)
|
||||
|
||||
func TestChartSize(t *testing.T) {
|
||||
|
||||
var buffer bytes.Buffer
|
||||
|
||||
categories := map[string]string{"A2": "Small", "A3": "Normal", "A4": "Large", "B1": "Apple", "C1": "Orange", "D1": "Pear"}
|
||||
values := map[string]int{"B2": 2, "C2": 3, "D2": 3, "B3": 5, "C3": 2, "D3": 4, "B4": 6, "C4": 7, "D4": 8}
|
||||
xlsx := NewFile()
|
||||
for k, v := range categories {
|
||||
xlsx.SetCellValue("Sheet1", k, v)
|
||||
sheet1 := xlsx.GetSheetName(1)
|
||||
|
||||
categories := map[string]string{
|
||||
"A2": "Small",
|
||||
"A3": "Normal",
|
||||
"A4": "Large",
|
||||
"B1": "Apple",
|
||||
"C1": "Orange",
|
||||
"D1": "Pear",
|
||||
}
|
||||
for k, v := range values {
|
||||
xlsx.SetCellValue("Sheet1", k, v)
|
||||
for cell, v := range categories {
|
||||
xlsx.SetCellValue(sheet1, cell, v)
|
||||
}
|
||||
xlsx.AddChart("Sheet1", "E4", `{"type":"col3DClustered","dimension":{"width":640, "height":480},"series":[{"name":"Sheet1!$A$2","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$2:$D$2"},{"name":"Sheet1!$A$3","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$3:$D$3"},{"name":"Sheet1!$A$4","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$4:$D$4"}],"title":{"name":"Fruit 3D Clustered Column Chart"}}`)
|
||||
|
||||
values := map[string]int{
|
||||
"B2": 2,
|
||||
"C2": 3,
|
||||
"D2": 3,
|
||||
"B3": 5,
|
||||
"C3": 2,
|
||||
"D3": 4,
|
||||
"B4": 6,
|
||||
"C4": 7,
|
||||
"D4": 8,
|
||||
}
|
||||
for cell, v := range values {
|
||||
xlsx.SetCellValue(sheet1, cell, v)
|
||||
}
|
||||
|
||||
xlsx.AddChart("Sheet1", "E4", `{"type":"col3DClustered","dimension":{"width":640, "height":480},`+
|
||||
`"series":[{"name":"Sheet1!$A$2","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$2:$D$2"},`+
|
||||
`{"name":"Sheet1!$A$3","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$3:$D$3"},`+
|
||||
`{"name":"Sheet1!$A$4","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$4:$D$4"}],`+
|
||||
`"title":{"name":"Fruit 3D Clustered Column Chart"}}`)
|
||||
|
||||
var (
|
||||
buffer bytes.Buffer
|
||||
)
|
||||
|
||||
// Save xlsx file by the given path.
|
||||
err := xlsx.Write(&buffer)
|
||||
if !assert.NoError(t, err) {
|
||||
|
@ -51,7 +78,8 @@ func TestChartSize(t *testing.T) {
|
|||
t.FailNow()
|
||||
}
|
||||
|
||||
err = xml.Unmarshal([]byte("<decodeTwoCellAnchor>"+workdir.TwoCellAnchor[0].Content+"</decodeTwoCellAnchor>"), &anchor)
|
||||
err = xml.Unmarshal([]byte("<decodeTwoCellAnchor>"+
|
||||
workdir.TwoCellAnchor[0].Content+"</decodeTwoCellAnchor>"), &anchor)
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
|
166
col.go
166
col.go
|
@ -10,10 +10,7 @@
|
|||
package excelize
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Define the default cell size and EMU unit of measurement.
|
||||
|
@ -29,16 +26,19 @@ const (
|
|||
//
|
||||
// xlsx.GetColVisible("Sheet1", "D")
|
||||
//
|
||||
func (f *File) GetColVisible(sheet, column string) bool {
|
||||
func (f *File) GetColVisible(sheet, col string) bool {
|
||||
colNum := MustColumnNameToNumber(col)
|
||||
|
||||
xlsx := f.workSheetReader(sheet)
|
||||
col := TitleToNumber(strings.ToUpper(column)) + 1
|
||||
visible := true
|
||||
if xlsx.Cols == nil {
|
||||
return visible
|
||||
return true
|
||||
}
|
||||
|
||||
visible := true
|
||||
for c := range xlsx.Cols.Col {
|
||||
if xlsx.Cols.Col[c].Min <= col && col <= xlsx.Cols.Col[c].Max {
|
||||
visible = !xlsx.Cols.Col[c].Hidden
|
||||
colData := &xlsx.Cols.Col[c]
|
||||
if colData.Min <= colNum && colNum <= colData.Max {
|
||||
visible = !colData.Hidden
|
||||
}
|
||||
}
|
||||
return visible
|
||||
|
@ -49,31 +49,31 @@ func (f *File) GetColVisible(sheet, column string) bool {
|
|||
//
|
||||
// xlsx.SetColVisible("Sheet1", "D", false)
|
||||
//
|
||||
func (f *File) SetColVisible(sheet, column string, visible bool) {
|
||||
xlsx := f.workSheetReader(sheet)
|
||||
c := TitleToNumber(strings.ToUpper(column)) + 1
|
||||
col := xlsxCol{
|
||||
Min: c,
|
||||
Max: c,
|
||||
func (f *File) SetColVisible(sheet, col string, visible bool) {
|
||||
colNum := MustColumnNameToNumber(col)
|
||||
colData := xlsxCol{
|
||||
Min: colNum,
|
||||
Max: colNum,
|
||||
Hidden: !visible,
|
||||
CustomWidth: true,
|
||||
}
|
||||
xlsx := f.workSheetReader(sheet)
|
||||
if xlsx.Cols == nil {
|
||||
cols := xlsxCols{}
|
||||
cols.Col = append(cols.Col, col)
|
||||
cols.Col = append(cols.Col, colData)
|
||||
xlsx.Cols = &cols
|
||||
return
|
||||
}
|
||||
for v := range xlsx.Cols.Col {
|
||||
if xlsx.Cols.Col[v].Min <= c && c <= xlsx.Cols.Col[v].Max {
|
||||
col = xlsx.Cols.Col[v]
|
||||
if xlsx.Cols.Col[v].Min <= colNum && colNum <= xlsx.Cols.Col[v].Max {
|
||||
colData = xlsx.Cols.Col[v]
|
||||
}
|
||||
}
|
||||
col.Min = c
|
||||
col.Max = c
|
||||
col.Hidden = !visible
|
||||
col.CustomWidth = true
|
||||
xlsx.Cols.Col = append(xlsx.Cols.Col, col)
|
||||
colData.Min = colNum
|
||||
colData.Max = colNum
|
||||
colData.Hidden = !visible
|
||||
colData.CustomWidth = true
|
||||
xlsx.Cols.Col = append(xlsx.Cols.Col, colData)
|
||||
}
|
||||
|
||||
// GetColOutlineLevel provides a function to get outline level of a single
|
||||
|
@ -82,16 +82,17 @@ func (f *File) SetColVisible(sheet, column string, visible bool) {
|
|||
//
|
||||
// xlsx.GetColOutlineLevel("Sheet1", "D")
|
||||
//
|
||||
func (f *File) GetColOutlineLevel(sheet, column string) uint8 {
|
||||
func (f *File) GetColOutlineLevel(sheet, col string) uint8 {
|
||||
colNum := MustColumnNameToNumber(col)
|
||||
xlsx := f.workSheetReader(sheet)
|
||||
col := TitleToNumber(strings.ToUpper(column)) + 1
|
||||
level := uint8(0)
|
||||
if xlsx.Cols == nil {
|
||||
return level
|
||||
}
|
||||
for c := range xlsx.Cols.Col {
|
||||
if xlsx.Cols.Col[c].Min <= col && col <= xlsx.Cols.Col[c].Max {
|
||||
level = xlsx.Cols.Col[c].OutlineLevel
|
||||
colData := &xlsx.Cols.Col[c]
|
||||
if colData.Min <= colNum && colNum <= colData.Max {
|
||||
level = colData.OutlineLevel
|
||||
}
|
||||
}
|
||||
return level
|
||||
|
@ -103,31 +104,31 @@ func (f *File) GetColOutlineLevel(sheet, column string) uint8 {
|
|||
//
|
||||
// xlsx.SetColOutlineLevel("Sheet1", "D", 2)
|
||||
//
|
||||
func (f *File) SetColOutlineLevel(sheet, column string, level uint8) {
|
||||
xlsx := f.workSheetReader(sheet)
|
||||
c := TitleToNumber(strings.ToUpper(column)) + 1
|
||||
col := xlsxCol{
|
||||
Min: c,
|
||||
Max: c,
|
||||
func (f *File) SetColOutlineLevel(sheet, col string, level uint8) {
|
||||
colNum := MustColumnNameToNumber(col)
|
||||
colData := xlsxCol{
|
||||
Min: colNum,
|
||||
Max: colNum,
|
||||
OutlineLevel: level,
|
||||
CustomWidth: true,
|
||||
}
|
||||
xlsx := f.workSheetReader(sheet)
|
||||
if xlsx.Cols == nil {
|
||||
cols := xlsxCols{}
|
||||
cols.Col = append(cols.Col, col)
|
||||
cols.Col = append(cols.Col, colData)
|
||||
xlsx.Cols = &cols
|
||||
return
|
||||
}
|
||||
for v := range xlsx.Cols.Col {
|
||||
if xlsx.Cols.Col[v].Min <= c && c <= xlsx.Cols.Col[v].Max {
|
||||
col = xlsx.Cols.Col[v]
|
||||
if xlsx.Cols.Col[v].Min <= colNum && colNum <= xlsx.Cols.Col[v].Max {
|
||||
colData = xlsx.Cols.Col[v]
|
||||
}
|
||||
}
|
||||
col.Min = c
|
||||
col.Max = c
|
||||
col.OutlineLevel = level
|
||||
col.CustomWidth = true
|
||||
xlsx.Cols.Col = append(xlsx.Cols.Col, col)
|
||||
colData.Min = colNum
|
||||
colData.Max = colNum
|
||||
colData.OutlineLevel = level
|
||||
colData.CustomWidth = true
|
||||
xlsx.Cols.Col = append(xlsx.Cols.Col, colData)
|
||||
}
|
||||
|
||||
// SetColWidth provides a function to set the width of a single column or
|
||||
|
@ -141,11 +142,12 @@ func (f *File) SetColOutlineLevel(sheet, column string, level uint8) {
|
|||
// }
|
||||
//
|
||||
func (f *File) SetColWidth(sheet, startcol, endcol string, width float64) {
|
||||
min := TitleToNumber(strings.ToUpper(startcol)) + 1
|
||||
max := TitleToNumber(strings.ToUpper(endcol)) + 1
|
||||
min := MustColumnNameToNumber(startcol)
|
||||
max := MustColumnNameToNumber(endcol)
|
||||
if min > max {
|
||||
min, max = max, min
|
||||
}
|
||||
|
||||
xlsx := f.workSheetReader(sheet)
|
||||
col := xlsxCol{
|
||||
Min: min,
|
||||
|
@ -214,38 +216,38 @@ func (f *File) SetColWidth(sheet, startcol, endcol string, width float64) {
|
|||
// xAbs # Absolute distance to left side of object.
|
||||
// yAbs # Absolute distance to top side of object.
|
||||
//
|
||||
func (f *File) positionObjectPixels(sheet string, colStart, rowStart, x1, y1, width, height int) (int, int, int, int, int, int, int, int) {
|
||||
func (f *File) positionObjectPixels(sheet string, col, row, x1, y1, width, height int) (int, int, int, int, int, int, int, int) {
|
||||
xAbs := 0
|
||||
yAbs := 0
|
||||
|
||||
// Calculate the absolute x offset of the top-left vertex.
|
||||
for colID := 1; colID <= colStart; colID++ {
|
||||
for colID := 1; colID <= col; colID++ {
|
||||
xAbs += f.getColWidth(sheet, colID)
|
||||
}
|
||||
xAbs += x1
|
||||
|
||||
// Calculate the absolute y offset of the top-left vertex.
|
||||
// Store the column change to allow optimisations.
|
||||
for rowID := 1; rowID <= rowStart; rowID++ {
|
||||
for rowID := 1; rowID <= row; rowID++ {
|
||||
yAbs += f.getRowHeight(sheet, rowID)
|
||||
}
|
||||
yAbs += y1
|
||||
|
||||
// Adjust start column for offsets that are greater than the col width.
|
||||
for x1 >= f.getColWidth(sheet, colStart) {
|
||||
x1 -= f.getColWidth(sheet, colStart)
|
||||
colStart++
|
||||
for x1 >= f.getColWidth(sheet, col) {
|
||||
x1 -= f.getColWidth(sheet, col)
|
||||
col++
|
||||
}
|
||||
|
||||
// Adjust start row for offsets that are greater than the row height.
|
||||
for y1 >= f.getRowHeight(sheet, rowStart) {
|
||||
y1 -= f.getRowHeight(sheet, rowStart)
|
||||
rowStart++
|
||||
for y1 >= f.getRowHeight(sheet, row) {
|
||||
y1 -= f.getRowHeight(sheet, row)
|
||||
row++
|
||||
}
|
||||
|
||||
// Initialise end cell to the same as the start cell.
|
||||
colEnd := colStart
|
||||
rowEnd := rowStart
|
||||
colEnd := col
|
||||
rowEnd := row
|
||||
|
||||
width += x1
|
||||
height += y1
|
||||
|
@ -265,7 +267,7 @@ func (f *File) positionObjectPixels(sheet string, colStart, rowStart, x1, y1, wi
|
|||
// The end vertices are whatever is left from the width and height.
|
||||
x2 := width
|
||||
y2 := height
|
||||
return colStart, rowStart, xAbs, yAbs, colEnd, rowEnd, x2, y2
|
||||
return col, row, xAbs, yAbs, colEnd, rowEnd, x2, y2
|
||||
}
|
||||
|
||||
// getColWidth provides a function to get column width in pixels by given
|
||||
|
@ -289,13 +291,13 @@ func (f *File) getColWidth(sheet string, col int) int {
|
|||
|
||||
// GetColWidth provides a function to get column width by given worksheet name
|
||||
// and column index.
|
||||
func (f *File) GetColWidth(sheet, column string) float64 {
|
||||
col := TitleToNumber(strings.ToUpper(column)) + 1
|
||||
func (f *File) GetColWidth(sheet, col string) float64 {
|
||||
colNum := MustColumnNameToNumber(col)
|
||||
xlsx := f.workSheetReader(sheet)
|
||||
if xlsx.Cols != nil {
|
||||
var width float64
|
||||
for _, v := range xlsx.Cols.Col {
|
||||
if v.Min <= col && col <= v.Max {
|
||||
if v.Min <= colNum && colNum <= v.Max {
|
||||
width = v.Width
|
||||
}
|
||||
}
|
||||
|
@ -312,9 +314,12 @@ func (f *File) GetColWidth(sheet, column string) float64 {
|
|||
//
|
||||
// xlsx.InsertCol("Sheet1", "C")
|
||||
//
|
||||
func (f *File) InsertCol(sheet, column string) {
|
||||
col := TitleToNumber(strings.ToUpper(column))
|
||||
f.adjustHelper(sheet, col, -1, 1)
|
||||
func (f *File) InsertCol(sheet, col string) {
|
||||
num, err := ColumnNameToNumber(col)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
f.adjustHelper(sheet, columns, num, 1)
|
||||
}
|
||||
|
||||
// RemoveCol provides a function to remove single column by given worksheet
|
||||
|
@ -326,38 +331,23 @@ func (f *File) InsertCol(sheet, column string) {
|
|||
// as formulas, charts, and so on. If there is any referenced value of the
|
||||
// worksheet, it will cause a file error when you open it. The excelize only
|
||||
// partially updates these references currently.
|
||||
func (f *File) RemoveCol(sheet, column string) {
|
||||
xlsx := f.workSheetReader(sheet)
|
||||
for r := range xlsx.SheetData.Row {
|
||||
for k, v := range xlsx.SheetData.Row[r].C {
|
||||
axis := v.R
|
||||
col := string(strings.Map(letterOnlyMapF, axis))
|
||||
if col == column {
|
||||
xlsx.SheetData.Row[r].C = append(xlsx.SheetData.Row[r].C[:k], xlsx.SheetData.Row[r].C[k+1:]...)
|
||||
func (f *File) RemoveCol(sheet, col string) {
|
||||
num, err := ColumnNameToNumber(col)
|
||||
if err != nil {
|
||||
panic(err) // Fail fast to avoid possible future side effects!
|
||||
}
|
||||
}
|
||||
}
|
||||
col := TitleToNumber(strings.ToUpper(column))
|
||||
f.adjustHelper(sheet, col, -1, -1)
|
||||
}
|
||||
|
||||
// completeCol provieds function to completion column element tags of XML in a
|
||||
// sheet.
|
||||
func completeCol(xlsx *xlsxWorksheet, row, cell int) {
|
||||
buffer := bytes.Buffer{}
|
||||
for r := range xlsx.SheetData.Row {
|
||||
if len(xlsx.SheetData.Row[r].C) < cell {
|
||||
start := len(xlsx.SheetData.Row[r].C)
|
||||
for iii := start; iii < cell; iii++ {
|
||||
buffer.WriteString(ToAlphaString(iii))
|
||||
buffer.WriteString(strconv.Itoa(r + 1))
|
||||
xlsx.SheetData.Row[r].C = append(xlsx.SheetData.Row[r].C, xlsxC{
|
||||
R: buffer.String(),
|
||||
})
|
||||
buffer.Reset()
|
||||
xlsx := f.workSheetReader(sheet)
|
||||
for rowIdx := range xlsx.SheetData.Row {
|
||||
rowData := xlsx.SheetData.Row[rowIdx]
|
||||
for colIdx, cellData := range rowData.C {
|
||||
colName, _, _ := SplitCellName(cellData.R)
|
||||
if colName == col {
|
||||
rowData.C = append(rowData.C[:colIdx], rowData.C[colIdx+1:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
f.adjustHelper(sheet, columns, num, -1)
|
||||
}
|
||||
|
||||
// convertColWidthToPixels provieds function to convert the width of a cell
|
||||
|
|
|
@ -115,10 +115,9 @@ func (f *File) AddComment(sheet, cell, format string) error {
|
|||
// addDrawingVML provides a function to create comment as
|
||||
// xl/drawings/vmlDrawing%d.vml by given commit ID and cell.
|
||||
func (f *File) addDrawingVML(commentID int, drawingVML, cell string, lineCount, colCount int) {
|
||||
col := string(strings.Map(letterOnlyMapF, cell))
|
||||
row, _ := strconv.Atoi(strings.Map(intOnlyMapF, cell))
|
||||
col, row := MustCellNameToCoordinates(cell)
|
||||
yAxis := col - 1
|
||||
xAxis := row - 1
|
||||
yAxis := TitleToNumber(col)
|
||||
vml := f.VMLDrawing[drawingVML]
|
||||
if vml == nil {
|
||||
vml = &vmlDrawing{
|
||||
|
|
60
date.go
60
date.go
|
@ -14,31 +14,53 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
// timeLocationUTC defined the UTC time location.
|
||||
var timeLocationUTC, _ = time.LoadLocation("UTC")
|
||||
const (
|
||||
dayNanoseconds = 24 * time.Hour
|
||||
maxDuration = 290 * 364 * dayNanoseconds
|
||||
)
|
||||
|
||||
// timeToUTCTime provides a function to convert time to UTC time.
|
||||
func timeToUTCTime(t time.Time) time.Time {
|
||||
return time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), timeLocationUTC)
|
||||
}
|
||||
var (
|
||||
excelMinTime1900 = time.Date(1899, time.December, 31, 0, 0, 0, 0, time.UTC)
|
||||
excelBuggyPeriodStart = time.Date(1900, time.March, 1, 0, 0, 0, 0, time.UTC).Add(-time.Nanosecond)
|
||||
)
|
||||
|
||||
// timeToExcelTime provides a function to convert time to Excel time.
|
||||
func timeToExcelTime(t time.Time) float64 {
|
||||
// TODO in future this should probably also handle date1904 and like TimeFromExcelTime
|
||||
var excelTime float64
|
||||
var deltaDays int64
|
||||
excelTime = 0
|
||||
deltaDays = 290 * 364
|
||||
// check if UnixNano would be out of int64 range
|
||||
for t.Unix() > deltaDays*24*60*60 {
|
||||
// reduce by aprox. 290 years, which is max for int64 nanoseconds
|
||||
delta := time.Duration(deltaDays) * 24 * time.Hour
|
||||
excelTime = excelTime + float64(deltaDays)
|
||||
t = t.Add(-delta)
|
||||
|
||||
// Force user to explicit convet passed value to UTC time.
|
||||
// Because for example 1900-01-01 00:00:00 +0300 MSK converts to 1900-01-01 00:00:00 +0230 LMT
|
||||
// probably due to daylight saving.
|
||||
if t.Location() != time.UTC {
|
||||
panic("only UTC time expected")
|
||||
}
|
||||
// finally add remainder of UnixNano to keep nano precision
|
||||
// and 25569 which is days between 1900 and 1970
|
||||
return excelTime + float64(t.UnixNano())/8.64e13 + 25569.0
|
||||
|
||||
if t.Before(excelMinTime1900) {
|
||||
return 0.0
|
||||
}
|
||||
|
||||
tt := t
|
||||
diff := t.Sub(excelMinTime1900)
|
||||
result := float64(0)
|
||||
|
||||
for diff >= maxDuration {
|
||||
result += float64(maxDuration / dayNanoseconds)
|
||||
tt = tt.Add(-maxDuration)
|
||||
diff = tt.Sub(excelMinTime1900)
|
||||
}
|
||||
|
||||
rem := diff % dayNanoseconds
|
||||
result += float64(diff-rem)/float64(dayNanoseconds) + float64(rem)/float64(dayNanoseconds)
|
||||
|
||||
// Excel dates after 28th February 1900 are actually one day out.
|
||||
// Excel behaves as though the date 29th February 1900 existed, which it didn't.
|
||||
// Microsoft intentionally included this bug in Excel so that it would remain compatible with the spreadsheet
|
||||
// program that had the majority market share at the time; Lotus 1-2-3.
|
||||
// https://www.myonlinetraininghub.com/excel-date-and-time
|
||||
if t.After(excelBuggyPeriodStart) {
|
||||
result += 1.0
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// shiftJulianToNoon provides a function to process julian date to noon.
|
||||
|
|
47
date_test.go
47
date_test.go
|
@ -13,17 +13,40 @@ type dateTest struct {
|
|||
GoValue time.Time
|
||||
}
|
||||
|
||||
func TestTimeToExcelTime(t *testing.T) {
|
||||
trueExpectedInputList := []dateTest{
|
||||
{0.0, time.Date(1899, 12, 30, 0, 0, 0, 0, time.UTC)},
|
||||
{25569.0, time.Unix(0, 0)},
|
||||
{43269.0, time.Date(2018, 6, 18, 0, 0, 0, 0, time.UTC)},
|
||||
{401769.0, time.Date(3000, 1, 1, 0, 0, 0, 0, time.UTC)},
|
||||
}
|
||||
var trueExpectedDateList = []dateTest{
|
||||
{0.0000000000000000, time.Date(1899, time.December, 30, 0, 0, 0, 0, time.UTC)},
|
||||
{25569.000000000000, time.Unix(0, 0).UTC()},
|
||||
|
||||
for i, test := range trueExpectedInputList {
|
||||
// Expected values extracted from real xlsx file
|
||||
{1.0000000000000000, time.Date(1900, time.January, 1, 0, 0, 0, 0, time.UTC)},
|
||||
{1.0000115740740740, time.Date(1900, time.January, 1, 0, 0, 1, 0, time.UTC)},
|
||||
{1.0006944444444446, time.Date(1900, time.January, 1, 0, 1, 0, 0, time.UTC)},
|
||||
{1.0416666666666667, time.Date(1900, time.January, 1, 1, 0, 0, 0, time.UTC)},
|
||||
{2.0000000000000000, time.Date(1900, time.January, 2, 0, 0, 0, 0, time.UTC)},
|
||||
{43269.000000000000, time.Date(2018, time.June, 18, 0, 0, 0, 0, time.UTC)},
|
||||
{43542.611111111109, time.Date(2019, time.March, 18, 14, 40, 0, 0, time.UTC)},
|
||||
{401769.00000000000, time.Date(3000, time.January, 1, 0, 0, 0, 0, time.UTC)},
|
||||
}
|
||||
|
||||
func TestTimeToExcelTime(t *testing.T) {
|
||||
for i, test := range trueExpectedDateList {
|
||||
t.Run(fmt.Sprintf("TestData%d", i+1), func(t *testing.T) {
|
||||
assert.Equal(t, test.ExcelValue, timeToExcelTime(test.GoValue))
|
||||
assert.Equalf(t, test.ExcelValue, timeToExcelTime(test.GoValue),
|
||||
"Time: %s", test.GoValue.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimeToExcelTime_Timezone(t *testing.T) {
|
||||
msk, err := time.LoadLocation("Europe/Moscow")
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
for i, test := range trueExpectedDateList {
|
||||
t.Run(fmt.Sprintf("TestData%d", i+1), func(t *testing.T) {
|
||||
assert.Panics(t, func() {
|
||||
timeToExcelTime(test.GoValue.In(msk))
|
||||
}, "Time: %s", test.GoValue.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -43,3 +66,9 @@ func TestTimeFromExcelTime(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimeFromExcelTime_1904(t *testing.T) {
|
||||
shiftJulianToNoon(1, -0.6)
|
||||
timeFromExcelTime(61, true)
|
||||
timeFromExcelTime(62, true)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
package excelize
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func newInvalidColumnNameError(col string) error {
|
||||
return fmt.Errorf("invalid column name %q", col)
|
||||
}
|
||||
|
||||
func newInvalidRowNumberError(row int) error {
|
||||
return fmt.Errorf("invalid row number %d", row)
|
||||
}
|
||||
|
||||
func newInvalidCellNameError(cell string) error {
|
||||
return fmt.Errorf("invalid cell name %q", cell)
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package excelize
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewInvalidColNameError(t *testing.T) {
|
||||
assert.EqualError(t, newInvalidColumnNameError("A"), "invalid column name \"A\"")
|
||||
assert.EqualError(t, newInvalidColumnNameError(""), "invalid column name \"\"")
|
||||
}
|
||||
|
||||
func TestNewInvalidRowNumberError(t *testing.T) {
|
||||
assert.EqualError(t, newInvalidRowNumberError(0), "invalid row number 0")
|
||||
}
|
||||
|
||||
func TestNewInvalidCellNameError(t *testing.T) {
|
||||
assert.EqualError(t, newInvalidCellNameError("A"), "invalid cell name \"A\"")
|
||||
assert.EqualError(t, newInvalidCellNameError(""), "invalid cell name \"\"")
|
||||
}
|
231
excelize.go
231
excelize.go
|
@ -206,235 +206,16 @@ func (f *File) UpdateLinkedValue() {
|
|||
}
|
||||
}
|
||||
|
||||
// adjustHelper provides a function to adjust rows and columns dimensions,
|
||||
// hyperlinks, merged cells and auto filter when inserting or deleting rows or
|
||||
// columns.
|
||||
//
|
||||
// sheet: Worksheet name 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: adjustCalcChain, 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 a 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 a 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 {
|
||||
f.ajustSingleRowDimensions(&xlsx.SheetData.Row[i], r.R+offset)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ajustSingleRowDimensions provides a function to ajust single row dimensions.
|
||||
func (f *File) ajustSingleRowDimensions(r *xlsxRow, row int) {
|
||||
r.R = row
|
||||
for i, col := range r.C {
|
||||
r.C[i].R = string(strings.Map(letterOnlyMapF, col.R)) + strconv.Itoa(r.R)
|
||||
}
|
||||
}
|
||||
|
||||
// adjustHyperlinks provides a 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 a 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 a 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 a 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 a 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetMergeCells provides a function to get all merged cells from a worksheet currently.
|
||||
func (f *File) GetMergeCells(sheet string) []MergeCell {
|
||||
mergeCells := []MergeCell{}
|
||||
|
||||
xlsx := f.workSheetReader(sheet)
|
||||
|
||||
var mergeCells []MergeCell
|
||||
|
||||
if xlsx.MergeCells != nil {
|
||||
for i := 0; i < len(xlsx.MergeCells.Cells); i++ {
|
||||
mergeCells = make([]MergeCell, 0, len(xlsx.MergeCells.Cells))
|
||||
|
||||
for i := range xlsx.MergeCells.Cells {
|
||||
ref := xlsx.MergeCells.Cells[i].Ref
|
||||
axis := strings.Split(ref, ":")[0]
|
||||
mergeCells = append(mergeCells, []string{ref, f.GetCellValue(sheet, axis)})
|
||||
|
|
283
excelize_test.go
283
excelize_test.go
|
@ -35,13 +35,22 @@ func TestOpenFile(t *testing.T) {
|
|||
t.Log("\r\n")
|
||||
}
|
||||
xlsx.UpdateLinkedValue()
|
||||
|
||||
xlsx.SetCellDefault("Sheet2", "A1", strconv.FormatFloat(float64(100.1588), 'f', -1, 32))
|
||||
xlsx.SetCellDefault("Sheet2", "A1", strconv.FormatFloat(float64(-100.1588), 'f', -1, 64))
|
||||
|
||||
// Test set cell value with illegal row number.
|
||||
assert.Panics(t, func() {
|
||||
xlsx.SetCellDefault("Sheet2", "A", strconv.FormatFloat(float64(-100.1588), 'f', -1, 64))
|
||||
})
|
||||
|
||||
xlsx.SetCellInt("Sheet2", "A1", 100)
|
||||
|
||||
// Test set cell integer value with illegal row number.
|
||||
assert.Panics(t, func() {
|
||||
xlsx.SetCellInt("Sheet2", "A", 100)
|
||||
})
|
||||
|
||||
xlsx.SetCellStr("Sheet2", "C11", "Knowns")
|
||||
// Test max characters in a cell.
|
||||
xlsx.SetCellStr("Sheet2", "D11", strings.Repeat("c", 32769))
|
||||
|
@ -51,23 +60,38 @@ func TestOpenFile(t *testing.T) {
|
|||
xlsx.SetCellInt("Sheet3", "A23", 10)
|
||||
xlsx.SetCellStr("Sheet3", "b230", "10")
|
||||
xlsx.SetCellStr("Sheet10", "b230", "10")
|
||||
|
||||
// Test set cell string value with illegal row number.
|
||||
assert.Panics(t, func() {
|
||||
xlsx.SetCellStr("Sheet10", "A", "10")
|
||||
})
|
||||
|
||||
xlsx.SetActiveSheet(2)
|
||||
// Test get cell formula with given rows number.
|
||||
xlsx.GetCellFormula("Sheet1", "B19")
|
||||
// Test get cell formula with illegal worksheet name.
|
||||
xlsx.GetCellFormula("Sheet2", "B20")
|
||||
// Test get cell formula with illegal rows number.
|
||||
xlsx.GetCellFormula("Sheet1", "B20")
|
||||
|
||||
// Test get cell formula with illegal rows number.
|
||||
assert.Panics(t, func() {
|
||||
xlsx.GetCellFormula("Sheet1", "B")
|
||||
})
|
||||
|
||||
// Test get shared cell formula
|
||||
xlsx.GetCellFormula("Sheet2", "H11")
|
||||
xlsx.GetCellFormula("Sheet2", "I11")
|
||||
getSharedForumula(&xlsxWorksheet{}, "")
|
||||
|
||||
// Test read cell value with given illegal rows number.
|
||||
assert.Panics(t, func() {
|
||||
xlsx.GetCellValue("Sheet2", "a-1")
|
||||
})
|
||||
|
||||
assert.Panics(t, func() {
|
||||
xlsx.GetCellValue("Sheet2", "A")
|
||||
})
|
||||
|
||||
// Test read cell value with given lowercase column number.
|
||||
xlsx.GetCellValue("Sheet2", "a5")
|
||||
xlsx.GetCellValue("Sheet2", "C11")
|
||||
|
@ -92,10 +116,7 @@ func TestOpenFile(t *testing.T) {
|
|||
xlsx.SetCellValue("Sheet2", "F15", uint64(1<<32-1))
|
||||
xlsx.SetCellValue("Sheet2", "F16", true)
|
||||
xlsx.SetCellValue("Sheet2", "F17", complex64(5+10i))
|
||||
t.Log(letterOnlyMapF('x'))
|
||||
shiftJulianToNoon(1, -0.6)
|
||||
timeFromExcelTime(61, true)
|
||||
timeFromExcelTime(62, true)
|
||||
|
||||
// Test boolean write
|
||||
booltest := []struct {
|
||||
value bool
|
||||
|
@ -108,8 +129,14 @@ func TestOpenFile(t *testing.T) {
|
|||
xlsx.SetCellValue("Sheet2", "F16", test.value)
|
||||
assert.Equal(t, test.expected, xlsx.GetCellValue("Sheet2", "F16"))
|
||||
}
|
||||
|
||||
xlsx.SetCellValue("Sheet2", "G2", nil)
|
||||
|
||||
assert.Panics(t, func() {
|
||||
xlsx.SetCellValue("Sheet2", "G4", time.Now())
|
||||
})
|
||||
|
||||
xlsx.SetCellValue("Sheet2", "G4", time.Now().UTC())
|
||||
// 02:46:40
|
||||
xlsx.SetCellValue("Sheet2", "G5", time.Duration(1e13))
|
||||
// Test completion column.
|
||||
|
@ -298,8 +325,15 @@ func TestSetCellHyperLink(t *testing.T) {
|
|||
xlsx.SetCellHyperLink("Sheet2", "C1", "https://github.com/360EntSecGroup-Skylar/excelize", "External")
|
||||
// Test add Location hyperlink in a work sheet.
|
||||
xlsx.SetCellHyperLink("Sheet2", "D6", "Sheet1!D8", "Location")
|
||||
|
||||
assert.Panics(t, func() {
|
||||
xlsx.SetCellHyperLink("Sheet2", "C3", "Sheet1!D8", "")
|
||||
})
|
||||
|
||||
assert.Panics(t, func() {
|
||||
xlsx.SetCellHyperLink("Sheet2", "", "Sheet1!D60", "Location")
|
||||
})
|
||||
|
||||
assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestSetCellHyperLink.xlsx")))
|
||||
}
|
||||
|
||||
|
@ -309,9 +343,11 @@ func TestGetCellHyperLink(t *testing.T) {
|
|||
t.FailNow()
|
||||
}
|
||||
|
||||
link, target := xlsx.GetCellHyperLink("Sheet1", "")
|
||||
t.Log(link, target)
|
||||
link, target = xlsx.GetCellHyperLink("Sheet1", "A22")
|
||||
assert.Panics(t, func() {
|
||||
xlsx.GetCellHyperLink("Sheet1", "")
|
||||
})
|
||||
|
||||
link, target := xlsx.GetCellHyperLink("Sheet1", "A22")
|
||||
t.Log(link, target)
|
||||
link, target = xlsx.GetCellHyperLink("Sheet2", "D6")
|
||||
t.Log(link, target)
|
||||
|
@ -327,8 +363,12 @@ func TestSetCellFormula(t *testing.T) {
|
|||
|
||||
xlsx.SetCellFormula("Sheet1", "B19", "SUM(Sheet2!D2,Sheet2!D11)")
|
||||
xlsx.SetCellFormula("Sheet1", "C19", "SUM(Sheet2!D2,Sheet2!D9)")
|
||||
|
||||
// Test set cell formula with illegal rows number.
|
||||
assert.Panics(t, func() {
|
||||
xlsx.SetCellFormula("Sheet1", "C", "SUM(Sheet2!D2,Sheet2!D9)")
|
||||
})
|
||||
|
||||
assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestSetCellFormula1.xlsx")))
|
||||
|
||||
xlsx, err = OpenFile(filepath.Join("test", "CalcChain.xlsx"))
|
||||
|
@ -408,51 +448,39 @@ func TestGetMergeCells(t *testing.T) {
|
|||
value string
|
||||
start string
|
||||
end string
|
||||
}{
|
||||
{
|
||||
}{{
|
||||
value: "A1",
|
||||
start: "A1",
|
||||
end: "B1",
|
||||
},
|
||||
{
|
||||
}, {
|
||||
value: "A2",
|
||||
start: "A2",
|
||||
end: "A3",
|
||||
},
|
||||
{
|
||||
}, {
|
||||
value: "A4",
|
||||
start: "A4",
|
||||
end: "B5",
|
||||
},
|
||||
{
|
||||
}, {
|
||||
value: "A7",
|
||||
start: "A7",
|
||||
end: "C10",
|
||||
},
|
||||
}
|
||||
}}
|
||||
|
||||
xlsx, err := OpenFile(filepath.Join("test", "MergeCell.xlsx"))
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
sheet1 := xlsx.GetSheetName(1)
|
||||
|
||||
mergeCells := xlsx.GetMergeCells("Sheet1")
|
||||
if len(mergeCells) != len(wants) {
|
||||
t.Fatalf("Expected count of merge cells %d, but got %d\n", len(wants), len(mergeCells))
|
||||
mergeCells := xlsx.GetMergeCells(sheet1)
|
||||
if !assert.Len(t, mergeCells, len(wants)) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
for i, m := range mergeCells {
|
||||
if wants[i].value != m.GetCellValue() {
|
||||
t.Fatalf("Expected merged cell value %s, but got %s\n", wants[i].value, m.GetCellValue())
|
||||
}
|
||||
|
||||
if wants[i].start != m.GetStartAxis() {
|
||||
t.Fatalf("Expected merged cell value %s, but got %s\n", wants[i].start, m.GetStartAxis())
|
||||
}
|
||||
|
||||
if wants[i].end != m.GetEndAxis() {
|
||||
t.Fatalf("Expected merged cell value %s, but got %s\n", wants[i].end, m.GetEndAxis())
|
||||
}
|
||||
assert.Equal(t, wants[i].value, m.GetCellValue())
|
||||
assert.Equal(t, wants[i].start, m.GetStartAxis())
|
||||
assert.Equal(t, wants[i].end, m.GetEndAxis())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -469,11 +497,20 @@ func TestSetCellStyleAlignment(t *testing.T) {
|
|||
}
|
||||
|
||||
xlsx.SetCellStyle("Sheet1", "A22", "A22", style)
|
||||
|
||||
// Test set cell style with given illegal rows number.
|
||||
assert.Panics(t, func() {
|
||||
xlsx.SetCellStyle("Sheet1", "A", "A22", style)
|
||||
})
|
||||
|
||||
assert.Panics(t, func() {
|
||||
xlsx.SetCellStyle("Sheet1", "A22", "A", style)
|
||||
})
|
||||
|
||||
// Test get cell style with given illegal rows number.
|
||||
assert.Panics(t, func() {
|
||||
xlsx.GetCellStyle("Sheet1", "A")
|
||||
})
|
||||
|
||||
assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestSetCellStyleAlignment.xlsx")))
|
||||
}
|
||||
|
@ -782,46 +819,48 @@ func TestGetPicture(t *testing.T) {
|
|||
}
|
||||
|
||||
file, raw := xlsx.GetPicture("Sheet1", "F21")
|
||||
if file == "" {
|
||||
err = ioutil.WriteFile(file, raw, 0644)
|
||||
if !assert.NoError(t, err) {
|
||||
if !assert.NotEmpty(t, file) || !assert.NotEmpty(t, raw) ||
|
||||
!assert.NoError(t, ioutil.WriteFile(file, raw, 0644)) {
|
||||
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
// Try to get picture from a worksheet that doesn't contain any images.
|
||||
file, raw = xlsx.GetPicture("Sheet3", "I9")
|
||||
if file != "" {
|
||||
err = ioutil.WriteFile(file, raw, 0644)
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
assert.Empty(t, file)
|
||||
assert.Empty(t, raw)
|
||||
|
||||
// Try to get picture from a cell that doesn't contain an image.
|
||||
file, raw = xlsx.GetPicture("Sheet2", "A2")
|
||||
t.Log(file, len(raw))
|
||||
assert.Empty(t, file)
|
||||
assert.Empty(t, raw)
|
||||
|
||||
xlsx.getDrawingRelationships("xl/worksheets/_rels/sheet1.xml.rels", "rId8")
|
||||
xlsx.getDrawingRelationships("", "")
|
||||
xlsx.getSheetRelationshipsTargetByID("", "")
|
||||
xlsx.deleteSheetRelationships("", "")
|
||||
|
||||
// Try to get picture from a local storage file.
|
||||
assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestGetPicture.xlsx")))
|
||||
if !assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestGetPicture.xlsx"))) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
xlsx, err = OpenFile(filepath.Join("test", "TestGetPicture.xlsx"))
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
file, raw = xlsx.GetPicture("Sheet1", "F21")
|
||||
if file == "" {
|
||||
err = ioutil.WriteFile(file, raw, 0644)
|
||||
if !assert.NoError(t, err) {
|
||||
if !assert.NotEmpty(t, file) || !assert.NotEmpty(t, raw) ||
|
||||
!assert.NoError(t, ioutil.WriteFile(file, raw, 0644)) {
|
||||
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
// Try to get picture from a local storage file that doesn't contain an image.
|
||||
file, raw = xlsx.GetPicture("Sheet1", "F22")
|
||||
t.Log(file, len(raw))
|
||||
assert.Empty(t, file)
|
||||
assert.Empty(t, raw)
|
||||
}
|
||||
|
||||
func TestSheetVisibility(t *testing.T) {
|
||||
|
@ -838,21 +877,6 @@ func TestSheetVisibility(t *testing.T) {
|
|||
assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestSheetVisibility.xlsx")))
|
||||
}
|
||||
|
||||
func TestRowVisibility(t *testing.T) {
|
||||
xlsx, err := prepareTestBook1()
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
xlsx.SetRowVisible("Sheet3", 2, false)
|
||||
xlsx.SetRowVisible("Sheet3", 2, true)
|
||||
xlsx.SetRowVisible("Sheet3", 0, true)
|
||||
xlsx.GetRowVisible("Sheet3", 2)
|
||||
xlsx.GetRowVisible("Sheet3", 0)
|
||||
|
||||
assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestRowVisibility.xlsx")))
|
||||
}
|
||||
|
||||
func TestColumnVisibility(t *testing.T) {
|
||||
t.Run("TestBook1", func(t *testing.T) {
|
||||
xlsx, err := prepareTestBook1()
|
||||
|
@ -1065,38 +1089,37 @@ func TestAddChart(t *testing.T) {
|
|||
|
||||
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/360EntSecGroup-Skylar/excelize", "External")
|
||||
xlsx.MergeCell("Sheet1", "A1", "C3")
|
||||
err := xlsx.AutoFilter("Sheet1", "A2", "B2", `{"column":"B","expression":"x != blanks"}`)
|
||||
sheet1 := xlsx.GetSheetName(1)
|
||||
|
||||
fillCells(xlsx, sheet1, 10, 10)
|
||||
|
||||
xlsx.SetCellHyperLink(sheet1, "A5", "https://github.com/360EntSecGroup-Skylar/excelize", "External")
|
||||
xlsx.MergeCell(sheet1, "A1", "C3")
|
||||
|
||||
err := xlsx.AutoFilter(sheet1, "A2", "B2", `{"column":"B","expression":"x != blanks"}`)
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
xlsx.InsertCol("Sheet1", "A")
|
||||
xlsx.InsertCol(sheet1, "A")
|
||||
|
||||
assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestInsertCol.xlsx")))
|
||||
}
|
||||
|
||||
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/360EntSecGroup-Skylar/excelize", "External")
|
||||
xlsx.SetCellHyperLink("Sheet1", "C5", "https://github.com", "External")
|
||||
xlsx.MergeCell("Sheet1", "A1", "B1")
|
||||
xlsx.MergeCell("Sheet1", "A2", "B2")
|
||||
xlsx.RemoveCol("Sheet1", "A")
|
||||
xlsx.RemoveCol("Sheet1", "A")
|
||||
sheet1 := xlsx.GetSheetName(1)
|
||||
|
||||
fillCells(xlsx, sheet1, 10, 15)
|
||||
|
||||
xlsx.SetCellHyperLink(sheet1, "A5", "https://github.com/360EntSecGroup-Skylar/excelize", "External")
|
||||
xlsx.SetCellHyperLink(sheet1, "C5", "https://github.com", "External")
|
||||
|
||||
xlsx.MergeCell(sheet1, "A1", "B1")
|
||||
xlsx.MergeCell(sheet1, "A2", "B2")
|
||||
|
||||
xlsx.RemoveCol(sheet1, "A")
|
||||
xlsx.RemoveCol(sheet1, "A")
|
||||
|
||||
assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestRemoveCol.xlsx")))
|
||||
}
|
||||
|
@ -1117,11 +1140,10 @@ func TestSetPane(t *testing.T) {
|
|||
|
||||
func TestConditionalFormat(t *testing.T) {
|
||||
xlsx := NewFile()
|
||||
for j := 1; j <= 10; j++ {
|
||||
for i := 0; i <= 15; i++ {
|
||||
xlsx.SetCellInt("Sheet1", ToAlphaString(i)+strconv.Itoa(j), j)
|
||||
}
|
||||
}
|
||||
sheet1 := xlsx.GetSheetName(1)
|
||||
|
||||
fillCells(xlsx, sheet1, 10, 15)
|
||||
|
||||
var format1, format2, format3 int
|
||||
var err error
|
||||
// Rose format for bad conditional.
|
||||
|
@ -1143,31 +1165,31 @@ func TestConditionalFormat(t *testing.T) {
|
|||
}
|
||||
|
||||
// Color scales: 2 color.
|
||||
xlsx.SetConditionalFormat("Sheet1", "A1:A10", `[{"type":"2_color_scale","criteria":"=","min_type":"min","max_type":"max","min_color":"#F8696B","max_color":"#63BE7B"}]`)
|
||||
xlsx.SetConditionalFormat(sheet1, "A1:A10", `[{"type":"2_color_scale","criteria":"=","min_type":"min","max_type":"max","min_color":"#F8696B","max_color":"#63BE7B"}]`)
|
||||
// Color scales: 3 color.
|
||||
xlsx.SetConditionalFormat("Sheet1", "B1:B10", `[{"type":"3_color_scale","criteria":"=","min_type":"min","mid_type":"percentile","max_type":"max","min_color":"#F8696B","mid_color":"#FFEB84","max_color":"#63BE7B"}]`)
|
||||
xlsx.SetConditionalFormat(sheet1, "B1:B10", `[{"type":"3_color_scale","criteria":"=","min_type":"min","mid_type":"percentile","max_type":"max","min_color":"#F8696B","mid_color":"#FFEB84","max_color":"#63BE7B"}]`)
|
||||
// Hightlight cells rules: between...
|
||||
xlsx.SetConditionalFormat("Sheet1", "C1:C10", fmt.Sprintf(`[{"type":"cell","criteria":"between","format":%d,"minimum":"6","maximum":"8"}]`, format1))
|
||||
xlsx.SetConditionalFormat(sheet1, "C1:C10", fmt.Sprintf(`[{"type":"cell","criteria":"between","format":%d,"minimum":"6","maximum":"8"}]`, format1))
|
||||
// Hightlight cells rules: Greater Than...
|
||||
xlsx.SetConditionalFormat("Sheet1", "D1:D10", fmt.Sprintf(`[{"type":"cell","criteria":">","format":%d,"value":"6"}]`, format3))
|
||||
xlsx.SetConditionalFormat(sheet1, "D1:D10", fmt.Sprintf(`[{"type":"cell","criteria":">","format":%d,"value":"6"}]`, format3))
|
||||
// Hightlight cells rules: Equal To...
|
||||
xlsx.SetConditionalFormat("Sheet1", "E1:E10", fmt.Sprintf(`[{"type":"top","criteria":"=","format":%d}]`, format3))
|
||||
xlsx.SetConditionalFormat(sheet1, "E1:E10", fmt.Sprintf(`[{"type":"top","criteria":"=","format":%d}]`, format3))
|
||||
// Hightlight cells rules: Not Equal To...
|
||||
xlsx.SetConditionalFormat("Sheet1", "F1:F10", fmt.Sprintf(`[{"type":"unique","criteria":"=","format":%d}]`, format2))
|
||||
xlsx.SetConditionalFormat(sheet1, "F1:F10", fmt.Sprintf(`[{"type":"unique","criteria":"=","format":%d}]`, format2))
|
||||
// Hightlight cells rules: Duplicate Values...
|
||||
xlsx.SetConditionalFormat("Sheet1", "G1:G10", fmt.Sprintf(`[{"type":"duplicate","criteria":"=","format":%d}]`, format2))
|
||||
xlsx.SetConditionalFormat(sheet1, "G1:G10", fmt.Sprintf(`[{"type":"duplicate","criteria":"=","format":%d}]`, format2))
|
||||
// Top/Bottom rules: Top 10%.
|
||||
xlsx.SetConditionalFormat("Sheet1", "H1:H10", fmt.Sprintf(`[{"type":"top","criteria":"=","format":%d,"value":"6","percent":true}]`, format1))
|
||||
xlsx.SetConditionalFormat(sheet1, "H1:H10", fmt.Sprintf(`[{"type":"top","criteria":"=","format":%d,"value":"6","percent":true}]`, format1))
|
||||
// Top/Bottom rules: Above Average...
|
||||
xlsx.SetConditionalFormat("Sheet1", "I1:I10", fmt.Sprintf(`[{"type":"average","criteria":"=","format":%d, "above_average": true}]`, format3))
|
||||
xlsx.SetConditionalFormat(sheet1, "I1:I10", fmt.Sprintf(`[{"type":"average","criteria":"=","format":%d, "above_average": true}]`, format3))
|
||||
// Top/Bottom rules: Below Average...
|
||||
xlsx.SetConditionalFormat("Sheet1", "J1:J10", fmt.Sprintf(`[{"type":"average","criteria":"=","format":%d, "above_average": false}]`, format1))
|
||||
xlsx.SetConditionalFormat(sheet1, "J1:J10", fmt.Sprintf(`[{"type":"average","criteria":"=","format":%d, "above_average": false}]`, format1))
|
||||
// Data Bars: Gradient Fill.
|
||||
xlsx.SetConditionalFormat("Sheet1", "K1:K10", `[{"type":"data_bar", "criteria":"=", "min_type":"min","max_type":"max","bar_color":"#638EC6"}]`)
|
||||
xlsx.SetConditionalFormat(sheet1, "K1:K10", `[{"type":"data_bar", "criteria":"=", "min_type":"min","max_type":"max","bar_color":"#638EC6"}]`)
|
||||
// Use a formula to determine which cells to format.
|
||||
xlsx.SetConditionalFormat("Sheet1", "L1:L10", fmt.Sprintf(`[{"type":"formula", "criteria":"L2<3", "format":%d}]`, format1))
|
||||
xlsx.SetConditionalFormat(sheet1, "L1:L10", fmt.Sprintf(`[{"type":"formula", "criteria":"L2<3", "format":%d}]`, format1))
|
||||
// Test set invalid format set in conditional format
|
||||
xlsx.SetConditionalFormat("Sheet1", "L1:L10", "")
|
||||
xlsx.SetConditionalFormat(sheet1, "L1:L10", "")
|
||||
|
||||
err = xlsx.SaveAs(filepath.Join("test", "TestConditionalFormat.xlsx"))
|
||||
if !assert.NoError(t, err) {
|
||||
|
@ -1175,9 +1197,9 @@ func TestConditionalFormat(t *testing.T) {
|
|||
}
|
||||
|
||||
// Set conditional format with illegal valid type.
|
||||
xlsx.SetConditionalFormat("Sheet1", "K1:K10", `[{"type":"", "criteria":"=", "min_type":"min","max_type":"max","bar_color":"#638EC6"}]`)
|
||||
xlsx.SetConditionalFormat(sheet1, "K1:K10", `[{"type":"", "criteria":"=", "min_type":"min","max_type":"max","bar_color":"#638EC6"}]`)
|
||||
// Set conditional format with illegal criteria type.
|
||||
xlsx.SetConditionalFormat("Sheet1", "K1:K10", `[{"type":"data_bar", "criteria":"", "min_type":"min","max_type":"max","bar_color":"#638EC6"}]`)
|
||||
xlsx.SetConditionalFormat(sheet1, "K1:K10", `[{"type":"data_bar", "criteria":"", "min_type":"min","max_type":"max","bar_color":"#638EC6"}]`)
|
||||
|
||||
// Set conditional format with file without dxfs element shold not return error.
|
||||
xlsx, err = OpenFile(filepath.Join("test", "Book1.xlsx"))
|
||||
|
@ -1193,11 +1215,9 @@ func TestConditionalFormat(t *testing.T) {
|
|||
|
||||
func TestConditionalFormatError(t *testing.T) {
|
||||
xlsx := NewFile()
|
||||
for j := 1; j <= 10; j++ {
|
||||
for i := 0; i <= 15; i++ {
|
||||
xlsx.SetCellInt("Sheet1", ToAlphaString(i)+strconv.Itoa(j), j)
|
||||
}
|
||||
}
|
||||
sheet1 := xlsx.GetSheetName(1)
|
||||
|
||||
fillCells(xlsx, sheet1, 10, 15)
|
||||
|
||||
// Set conditional format with illegal JSON string should return error
|
||||
_, err := xlsx.NewConditionalStyle("")
|
||||
|
@ -1206,15 +1226,6 @@ func TestConditionalFormatError(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestTitleToNumber(t *testing.T) {
|
||||
assert.Equal(t, 0, TitleToNumber("A"))
|
||||
assert.Equal(t, 25, TitleToNumber("Z"))
|
||||
assert.Equal(t, 26, TitleToNumber("AA"))
|
||||
assert.Equal(t, 36, TitleToNumber("AK"))
|
||||
assert.Equal(t, 36, TitleToNumber("ak"))
|
||||
assert.Equal(t, 51, TitleToNumber("AZ"))
|
||||
}
|
||||
|
||||
func TestSharedStrings(t *testing.T) {
|
||||
xlsx, err := OpenFile(filepath.Join("test", "SharedStrings.xlsx"))
|
||||
if !assert.NoError(t, err) {
|
||||
|
@ -1229,10 +1240,19 @@ func TestSetSheetRow(t *testing.T) {
|
|||
t.FailNow()
|
||||
}
|
||||
|
||||
xlsx.SetSheetRow("Sheet1", "B27", &[]interface{}{"cell", nil, int32(42), float64(42), time.Now()})
|
||||
xlsx.SetSheetRow("Sheet1", "B27", &[]interface{}{"cell", nil, int32(42), float64(42), time.Now().UTC()})
|
||||
|
||||
assert.Panics(t, func() {
|
||||
xlsx.SetSheetRow("Sheet1", "", &[]interface{}{"cell", nil, 2})
|
||||
})
|
||||
|
||||
assert.Panics(t, func() {
|
||||
xlsx.SetSheetRow("Sheet1", "B27", []interface{}{})
|
||||
})
|
||||
|
||||
assert.Panics(t, func() {
|
||||
xlsx.SetSheetRow("Sheet1", "B27", &xlsx)
|
||||
})
|
||||
|
||||
assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestSetSheetRow.xlsx")))
|
||||
}
|
||||
|
@ -1245,10 +1265,17 @@ func TestOutlineLevel(t *testing.T) {
|
|||
xlsx.GetColOutlineLevel("Shee2", "A")
|
||||
xlsx.SetColWidth("Sheet2", "A", "D", 13)
|
||||
xlsx.SetColOutlineLevel("Sheet2", "B", 2)
|
||||
xlsx.SetRowOutlineLevel("Sheet1", 2, 1)
|
||||
xlsx.SetRowOutlineLevel("Sheet1", 2, 250)
|
||||
|
||||
assert.Panics(t, func() {
|
||||
xlsx.SetRowOutlineLevel("Sheet1", 0, 1)
|
||||
xlsx.GetRowOutlineLevel("Sheet1", 2)
|
||||
})
|
||||
|
||||
assert.Equal(t, uint8(250), xlsx.GetRowOutlineLevel("Sheet1", 2))
|
||||
|
||||
assert.Panics(t, func() {
|
||||
xlsx.GetRowOutlineLevel("Sheet1", 0)
|
||||
})
|
||||
err := xlsx.SaveAs(filepath.Join("test", "TestOutlineLevel.xlsx"))
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
|
@ -1258,7 +1285,6 @@ func TestOutlineLevel(t *testing.T) {
|
|||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
xlsx.SetColOutlineLevel("Sheet2", "B", 2)
|
||||
}
|
||||
|
||||
|
@ -1388,3 +1414,12 @@ func prepareTestBook4() (*File, error) {
|
|||
|
||||
return xlsx, nil
|
||||
}
|
||||
|
||||
func fillCells(xlsx *File, sheet string, colCount, rowCount int) {
|
||||
for col := 1; col <= colCount; col++ {
|
||||
for row := 1; row <= rowCount; row++ {
|
||||
cell := MustCoordinatesToCellName(col, row)
|
||||
xlsx.SetCellStr(sheet, cell, cell)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
244
lib.go
244
lib.go
|
@ -12,11 +12,11 @@ package excelize
|
|||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// ReadZipReader can be used to read an XLSX in memory without touching the
|
||||
|
@ -64,48 +64,32 @@ func readFile(file *zip.File) []byte {
|
|||
return buff.Bytes()
|
||||
}
|
||||
|
||||
// ToAlphaString provides a function to convert integer to Excel sheet column
|
||||
// title. For example convert 36 to column title AK:
|
||||
// SplitCellName splits cell name to column name and row number.
|
||||
//
|
||||
// excelize.ToAlphaString(36)
|
||||
// Example:
|
||||
//
|
||||
func ToAlphaString(value int) string {
|
||||
if value < 0 {
|
||||
return ""
|
||||
// excelize.SplitCellName("AK74") // return "AK", 74, nil
|
||||
//
|
||||
func SplitCellName(cell string) (string, int, error) {
|
||||
alpha := func(r rune) bool {
|
||||
return ('A' <= r && r <= 'Z') || ('a' <= r && r <= 'z')
|
||||
}
|
||||
var ans string
|
||||
i := value + 1
|
||||
for i > 0 {
|
||||
ans = string((i-1)%26+65) + ans
|
||||
i = (i - 1) / 26
|
||||
|
||||
if strings.IndexFunc(cell, alpha) == 0 {
|
||||
i := strings.LastIndexFunc(cell, alpha)
|
||||
if i >= 0 && i < len(cell)-1 {
|
||||
col, rowstr := cell[:i+1], cell[i+1:]
|
||||
if row, err := strconv.Atoi(rowstr); err == nil && row > 0 {
|
||||
return col, row, nil
|
||||
}
|
||||
return ans
|
||||
}
|
||||
}
|
||||
return "", -1, newInvalidCellNameError(cell)
|
||||
}
|
||||
|
||||
// TitleToNumber provides a function to convert Excel sheet column title to
|
||||
// int (this function doesn't do value check currently). For example convert
|
||||
// AK and ak to column title 36:
|
||||
//
|
||||
// excelize.TitleToNumber("AK")
|
||||
// excelize.TitleToNumber("ak")
|
||||
//
|
||||
func TitleToNumber(s string) int {
|
||||
weight := 1
|
||||
sum := 0
|
||||
for i := len(s) - 1; i >= 0; i-- {
|
||||
ch := s[i]
|
||||
if ch >= 'a' && ch <= 'z' {
|
||||
ch -= 32
|
||||
}
|
||||
sum += int(ch-'A'+1) * weight
|
||||
weight *= 26
|
||||
}
|
||||
return sum - 1
|
||||
}
|
||||
|
||||
// letterOnlyMapF is used in conjunction with strings.Map to return only the
|
||||
// characters A-Z and a-z in a string.
|
||||
func letterOnlyMapF(rune rune) rune {
|
||||
// JoinCellName joins cell name from column name and row number
|
||||
func JoinCellName(col string, row int) (string, error) {
|
||||
normCol := strings.Map(func(rune rune) rune {
|
||||
switch {
|
||||
case 'A' <= rune && rune <= 'Z':
|
||||
return rune
|
||||
|
@ -113,15 +97,144 @@ func letterOnlyMapF(rune rune) rune {
|
|||
return rune - 32
|
||||
}
|
||||
return -1
|
||||
}, col)
|
||||
if len(col) == 0 || len(col) != len(normCol) {
|
||||
return "", newInvalidColumnNameError(col)
|
||||
}
|
||||
if row < 1 {
|
||||
return "", newInvalidRowNumberError(row)
|
||||
}
|
||||
return fmt.Sprintf("%s%d", normCol, row), nil
|
||||
}
|
||||
|
||||
// intOnlyMapF is used in conjunction with strings.Map to return only the
|
||||
// numeric portions of a string.
|
||||
func intOnlyMapF(rune rune) rune {
|
||||
if rune >= 48 && rune < 58 {
|
||||
return rune
|
||||
// ColumnNameToNumber provides a function to convert Excel sheet
|
||||
// column name to int. Column name case insencitive
|
||||
// Function returns error if column name incorrect.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// excelize.ColumnNameToNumber("AK") // returns 37, nil
|
||||
//
|
||||
func ColumnNameToNumber(name string) (int, error) {
|
||||
if len(name) == 0 {
|
||||
return -1, newInvalidColumnNameError(name)
|
||||
}
|
||||
return -1
|
||||
col := 0
|
||||
multi := 1
|
||||
for i := len(name) - 1; i >= 0; i-- {
|
||||
r := name[i]
|
||||
if r >= 'A' && r <= 'Z' {
|
||||
col += int(r-'A'+1) * multi
|
||||
} else if r >= 'a' && r <= 'z' {
|
||||
col += int(r-'a'+1) * multi
|
||||
} else {
|
||||
return -1, newInvalidColumnNameError(name)
|
||||
}
|
||||
multi *= 26
|
||||
}
|
||||
return col, nil
|
||||
}
|
||||
|
||||
// MustColumnNameToNumber provides a function to convert Excel sheet column
|
||||
// name to int. Column name case insencitive.
|
||||
// Function returns error if column name incorrect.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// excelize.MustColumnNameToNumber("AK") // returns 37
|
||||
//
|
||||
func MustColumnNameToNumber(name string) int {
|
||||
n, err := ColumnNameToNumber(name)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// ColumnNumberToName provides a function to convert integer
|
||||
// to Excel sheet column title.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// excelize.ToAlphaString(37) // returns "AK", nil
|
||||
//
|
||||
func ColumnNumberToName(num int) (string, error) {
|
||||
if num < 1 {
|
||||
return "", fmt.Errorf("incorrect column number %d", num)
|
||||
}
|
||||
var col string
|
||||
for num > 0 {
|
||||
col = string((num-1)%26+65) + col
|
||||
num = (num - 1) / 26
|
||||
}
|
||||
return col, nil
|
||||
}
|
||||
|
||||
// CellNameToCoordinates converts alpha-numeric cell name
|
||||
// to [X, Y] coordinates or retrusn an error.
|
||||
//
|
||||
// Example:
|
||||
// CellCoordinates("A1") // returns 1, 1, nil
|
||||
// CellCoordinates("Z3") // returns 26, 3, nil
|
||||
//
|
||||
func CellNameToCoordinates(cell string) (int, int, error) {
|
||||
const msg = "cannot convert cell %q to coordinates: %v"
|
||||
|
||||
colname, row, err := SplitCellName(cell)
|
||||
if err != nil {
|
||||
return -1, -1, fmt.Errorf(msg, cell, err)
|
||||
}
|
||||
|
||||
col, err := ColumnNameToNumber(colname)
|
||||
if err != nil {
|
||||
return -1, -1, fmt.Errorf(msg, cell, err)
|
||||
}
|
||||
|
||||
return col, row, nil
|
||||
}
|
||||
|
||||
// MustCellNameToCoordinates converts alpha-numeric cell name
|
||||
// to [X, Y] coordinates or panics.
|
||||
//
|
||||
// Example:
|
||||
// MustCellNameToCoordinates("A1") // returns 1, 1
|
||||
// MustCellNameToCoordinates("Z3") // returns 26, 3
|
||||
//
|
||||
func MustCellNameToCoordinates(cell string) (int, int) {
|
||||
c, r, err := CellNameToCoordinates(cell)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return c, r
|
||||
}
|
||||
|
||||
// CoordinatesToCellName converts [X, Y] coordinates to alpha-numeric cell name or returns an error.
|
||||
//
|
||||
// Example:
|
||||
// CoordinatesToCellName(1, 1) // returns "A1", nil
|
||||
//
|
||||
func CoordinatesToCellName(col, row int) (string, error) {
|
||||
if col < 1 || row < 1 {
|
||||
return "", fmt.Errorf("invalid cell coordinates [%d, %d]", col, row)
|
||||
}
|
||||
colname, err := ColumnNumberToName(col)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid cell coordinates [%d, %d]: %v", col, row, err)
|
||||
}
|
||||
return fmt.Sprintf("%s%d", colname, row), nil
|
||||
}
|
||||
|
||||
// MustCoordinatesToCellName converts [X, Y] coordinates to alpha-numeric cell name or panics.
|
||||
//
|
||||
// Example:
|
||||
// MustCoordinatesToCellName(1, 1) // returns "A1"
|
||||
//
|
||||
func MustCoordinatesToCellName(col, row int) string {
|
||||
n, err := CoordinatesToCellName(col, row)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// boolPtr returns a pointer to a bool with the given value.
|
||||
|
@ -135,47 +248,6 @@ func defaultTrue(b *bool) bool {
|
|||
return *b
|
||||
}
|
||||
|
||||
// axisLowerOrEqualThan returns true if axis1 <= axis2 axis1/axis2 can be
|
||||
// either a column or a row axis, e.g. "A", "AAE", "42", "1", etc.
|
||||
//
|
||||
// For instance, the following comparisons are all true:
|
||||
//
|
||||
// "A" <= "B"
|
||||
// "A" <= "AA"
|
||||
// "B" <= "AA"
|
||||
// "BC" <= "ABCD" (in a XLSX sheet, the BC col comes before the ABCD col)
|
||||
// "1" <= "2"
|
||||
// "2" <= "11" (in a XLSX sheet, the row 2 comes before the row 11)
|
||||
// and so on
|
||||
func axisLowerOrEqualThan(axis1, axis2 string) bool {
|
||||
if len(axis1) < len(axis2) {
|
||||
return true
|
||||
} else if len(axis1) > len(axis2) {
|
||||
return false
|
||||
} else {
|
||||
return axis1 <= axis2
|
||||
}
|
||||
}
|
||||
|
||||
// getCellColRow returns the two parts of a cell identifier (its col and row)
|
||||
// as strings
|
||||
//
|
||||
// For instance:
|
||||
//
|
||||
// "C220" => "C", "220"
|
||||
// "aaef42" => "aaef", "42"
|
||||
// "" => "", ""
|
||||
func getCellColRow(cell string) (col, row string) {
|
||||
for index, rune := range cell {
|
||||
if unicode.IsDigit(rune) {
|
||||
return cell[:index], cell[index:]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return cell, ""
|
||||
}
|
||||
|
||||
// parseFormatSet provides a method to convert format string to []byte and
|
||||
// handle empty string.
|
||||
func parseFormatSet(formatSet string) []byte {
|
||||
|
@ -208,7 +280,9 @@ func namespaceStrictToTransitional(content []byte) []byte {
|
|||
// is great, numerous passwords will match the same hash. Here is the
|
||||
// algorithm to create the hash value:
|
||||
//
|
||||
// take the ASCII values of all characters shift left the first character 1 bit, the second 2 bits and so on (use only the lower 15 bits and rotate all higher bits, the highest bit of the 16-bit value is always 0 [signed short])
|
||||
// take the ASCII values of all characters shift left the first character 1 bit,
|
||||
// the second 2 bits and so on (use only the lower 15 bits and rotate all higher bits,
|
||||
// the highest bit of the 16-bit value is always 0 [signed short])
|
||||
// XOR all these values
|
||||
// XOR the count of characters
|
||||
// XOR the constant 0xCE4B
|
||||
|
|
234
lib_test.go
234
lib_test.go
|
@ -2,59 +2,213 @@ package excelize
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAxisLowerOrEqualThanIsTrue(t *testing.T) {
|
||||
trueExpectedInputList := [][2]string{
|
||||
{"A", "B"},
|
||||
{"A", "AA"},
|
||||
{"B", "AA"},
|
||||
{"BC", "ABCD"},
|
||||
{"1", "2"},
|
||||
{"2", "11"},
|
||||
}
|
||||
var validColumns = []struct {
|
||||
Name string
|
||||
Num int
|
||||
}{
|
||||
{Name: "A", Num: 1},
|
||||
{Name: "Z", Num: 26},
|
||||
{Name: "AA", Num: 26 + 1},
|
||||
{Name: "AK", Num: 26 + 11},
|
||||
{Name: "ak", Num: 26 + 11},
|
||||
{Name: "Ak", Num: 26 + 11},
|
||||
{Name: "aK", Num: 26 + 11},
|
||||
{Name: "AZ", Num: 26 + 26},
|
||||
{Name: "ZZ", Num: 26 + 26*26},
|
||||
{Name: "AAA", Num: 26 + 26*26 + 1},
|
||||
{Name: "ZZZ", Num: 26 + 26*26 + 26*26*26},
|
||||
}
|
||||
|
||||
for i, trueExpectedInput := range trueExpectedInputList {
|
||||
t.Run(fmt.Sprintf("TestData%d", i), func(t *testing.T) {
|
||||
assert.True(t, axisLowerOrEqualThan(trueExpectedInput[0], trueExpectedInput[1]))
|
||||
})
|
||||
var invalidColumns = []struct {
|
||||
Name string
|
||||
Num int
|
||||
}{
|
||||
{Name: "", Num: -1},
|
||||
{Name: " ", Num: -1},
|
||||
{Name: "_", Num: -1},
|
||||
{Name: "__", Num: -1},
|
||||
{Name: "-1", Num: -1},
|
||||
{Name: "0", Num: -1},
|
||||
{Name: " A", Num: -1},
|
||||
{Name: "A ", Num: -1},
|
||||
{Name: "A1", Num: -1},
|
||||
{Name: "1A", Num: -1},
|
||||
{Name: " a", Num: -1},
|
||||
{Name: "a ", Num: -1},
|
||||
{Name: "a1", Num: -1},
|
||||
{Name: "1a", Num: -1},
|
||||
{Name: " _", Num: -1},
|
||||
{Name: "_ ", Num: -1},
|
||||
{Name: "_1", Num: -1},
|
||||
{Name: "1_", Num: -1},
|
||||
}
|
||||
|
||||
var invalidCells = []string{"", "A", "AA", " A", "A ", "1A", "A1A", "A1 ", " A1", "1A1", "a-1", "A-1"}
|
||||
|
||||
var invalidIndexes = []int{-100, -2, -1, 0}
|
||||
|
||||
func TestColumnNameToNumber_OK(t *testing.T) {
|
||||
const msg = "Column %q"
|
||||
for _, col := range validColumns {
|
||||
out, err := ColumnNameToNumber(col.Name)
|
||||
if assert.NoErrorf(t, err, msg, col.Name) {
|
||||
assert.Equalf(t, col.Num, out, msg, col.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAxisLowerOrEqualThanIsFalse(t *testing.T) {
|
||||
falseExpectedInputList := [][2]string{
|
||||
{"B", "A"},
|
||||
{"AA", "A"},
|
||||
{"AA", "B"},
|
||||
{"ABCD", "AB"},
|
||||
{"2", "1"},
|
||||
{"11", "2"},
|
||||
func TestColumnNameToNumber_Error(t *testing.T) {
|
||||
const msg = "Column %q"
|
||||
for _, col := range invalidColumns {
|
||||
out, err := ColumnNameToNumber(col.Name)
|
||||
if assert.Errorf(t, err, msg, col.Name) {
|
||||
assert.Equalf(t, col.Num, out, msg, col.Name)
|
||||
}
|
||||
|
||||
for i, falseExpectedInput := range falseExpectedInputList {
|
||||
t.Run(fmt.Sprintf("TestData%d", i), func(t *testing.T) {
|
||||
assert.False(t, axisLowerOrEqualThan(falseExpectedInput[0], falseExpectedInput[1]))
|
||||
})
|
||||
assert.Panicsf(t, func() {
|
||||
MustColumnNameToNumber(col.Name)
|
||||
}, msg, col.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCellColRow(t *testing.T) {
|
||||
cellExpectedColRowList := [][3]string{
|
||||
{"C220", "C", "220"},
|
||||
{"aaef42", "aaef", "42"},
|
||||
{"bonjour", "bonjour", ""},
|
||||
{"59", "", "59"},
|
||||
{"", "", ""},
|
||||
func TestColumnNumberToName_OK(t *testing.T) {
|
||||
const msg = "Column %q"
|
||||
for _, col := range validColumns {
|
||||
out, err := ColumnNumberToName(col.Num)
|
||||
if assert.NoErrorf(t, err, msg, col.Name) {
|
||||
assert.Equalf(t, strings.ToUpper(col.Name), out, msg, col.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestColumnNumberToName_Error(t *testing.T) {
|
||||
out, err := ColumnNumberToName(-1)
|
||||
if assert.Error(t, err) {
|
||||
assert.Equal(t, "", out)
|
||||
}
|
||||
|
||||
out, err = ColumnNumberToName(0)
|
||||
if assert.Error(t, err) {
|
||||
assert.Equal(t, "", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitCellName_OK(t *testing.T) {
|
||||
const msg = "Cell \"%s%d\""
|
||||
for i, col := range validColumns {
|
||||
row := i + 1
|
||||
c, r, err := SplitCellName(col.Name + strconv.Itoa(row))
|
||||
if assert.NoErrorf(t, err, msg, col.Name, row) {
|
||||
assert.Equalf(t, col.Name, c, msg, col.Name, row)
|
||||
assert.Equalf(t, row, r, msg, col.Name, row)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitCellName_Error(t *testing.T) {
|
||||
const msg = "Cell %q"
|
||||
for _, cell := range invalidCells {
|
||||
c, r, err := SplitCellName(cell)
|
||||
if assert.Errorf(t, err, msg, cell) {
|
||||
assert.Equalf(t, "", c, msg, cell)
|
||||
assert.Equalf(t, -1, r, msg, cell)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestJoinCellName_OK(t *testing.T) {
|
||||
const msg = "Cell \"%s%d\""
|
||||
|
||||
for i, col := range validColumns {
|
||||
row := i + 1
|
||||
cell, err := JoinCellName(col.Name, row)
|
||||
if assert.NoErrorf(t, err, msg, col.Name, row) {
|
||||
assert.Equalf(t, strings.ToUpper(fmt.Sprintf("%s%d", col.Name, row)), cell, msg, row)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestJoinCellName_Error(t *testing.T) {
|
||||
const msg = "Cell \"%s%d\""
|
||||
|
||||
test := func(col string, row int) {
|
||||
cell, err := JoinCellName(col, row)
|
||||
if assert.Errorf(t, err, msg, col, row) {
|
||||
assert.Equalf(t, "", cell, msg, col, row)
|
||||
}
|
||||
}
|
||||
|
||||
for _, col := range invalidColumns {
|
||||
test(col.Name, 1)
|
||||
for _, row := range invalidIndexes {
|
||||
test("A", row)
|
||||
test(col.Name, row)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestCellNameToCoordinates_OK(t *testing.T) {
|
||||
const msg = "Cell \"%s%d\""
|
||||
for i, col := range validColumns {
|
||||
row := i + 1
|
||||
c, r, err := CellNameToCoordinates(col.Name + strconv.Itoa(row))
|
||||
if assert.NoErrorf(t, err, msg, col.Name, row) {
|
||||
assert.Equalf(t, col.Num, c, msg, col.Name, row)
|
||||
assert.Equalf(t, i+1, r, msg, col.Name, row)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCellNameToCoordinates_Error(t *testing.T) {
|
||||
const msg = "Cell %q"
|
||||
for _, cell := range invalidCells {
|
||||
c, r, err := CellNameToCoordinates(cell)
|
||||
if assert.Errorf(t, err, msg, cell) {
|
||||
assert.Equalf(t, -1, c, msg, cell)
|
||||
assert.Equalf(t, -1, r, msg, cell)
|
||||
}
|
||||
assert.Panicsf(t, func() {
|
||||
MustCellNameToCoordinates(cell)
|
||||
}, msg, cell)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCoordinatesToCellName_OK(t *testing.T) {
|
||||
const msg = "Coordinates [%d, %d]"
|
||||
for i, col := range validColumns {
|
||||
row := i + 1
|
||||
cell, err := CoordinatesToCellName(col.Num, row)
|
||||
if assert.NoErrorf(t, err, msg, col.Num, row) {
|
||||
assert.Equalf(t, strings.ToUpper(col.Name+strconv.Itoa(row)), cell, msg, col.Num, row)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCoordinatesToCellName_Error(t *testing.T) {
|
||||
const msg = "Coordinates [%d, %d]"
|
||||
|
||||
test := func(col, row int) {
|
||||
cell, err := CoordinatesToCellName(col, row)
|
||||
if assert.Errorf(t, err, msg, col, row) {
|
||||
assert.Equalf(t, "", cell, msg, col, row)
|
||||
}
|
||||
assert.Panicsf(t, func() {
|
||||
MustCoordinatesToCellName(col, row)
|
||||
}, msg, col, row)
|
||||
}
|
||||
|
||||
for _, col := range invalidIndexes {
|
||||
test(col, 1)
|
||||
for _, row := range invalidIndexes {
|
||||
test(1, row)
|
||||
test(col, row)
|
||||
}
|
||||
|
||||
for i, test := range cellExpectedColRowList {
|
||||
t.Run(fmt.Sprintf("TestData%d", i), func(t *testing.T) {
|
||||
col, row := getCellColRow(test[0])
|
||||
assert.Equal(t, test[1], col, "Unexpected col")
|
||||
assert.Equal(t, test[2], row, "Unexpected row")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
35
picture.go
35
picture.go
|
@ -143,7 +143,7 @@ func (f *File) AddPictureFromBytes(sheet, cell, format, name, extension string,
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
image, _, err := image.DecodeConfig(bytes.NewReader(file))
|
||||
img, _, err := image.DecodeConfig(bytes.NewReader(file))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -162,7 +162,7 @@ func (f *File) AddPictureFromBytes(sheet, cell, format, name, extension string,
|
|||
}
|
||||
drawingHyperlinkRID = f.addDrawingRelationships(drawingID, SourceRelationshipHyperLink, formatSet.Hyperlink, hyperlinkType)
|
||||
}
|
||||
f.addDrawingPicture(sheet, drawingXML, cell, name, image.Width, image.Height, drawingRID, drawingHyperlinkRID, formatSet)
|
||||
f.addDrawingPicture(sheet, drawingXML, cell, name, img.Width, img.Height, drawingRID, drawingHyperlinkRID, formatSet)
|
||||
f.addMedia(file, ext)
|
||||
f.addContentTypePart(drawingID, "drawings")
|
||||
return err
|
||||
|
@ -263,14 +263,11 @@ func (f *File) countDrawings() int {
|
|||
// drawingXML, cell, file name, width, height relationship index and format
|
||||
// sets.
|
||||
func (f *File) addDrawingPicture(sheet, drawingXML, cell, file string, width, height, rID, hyperlinkRID int, formatSet *formatPicture) {
|
||||
cell = strings.ToUpper(cell)
|
||||
fromCol := string(strings.Map(letterOnlyMapF, cell))
|
||||
fromRow, _ := strconv.Atoi(strings.Map(intOnlyMapF, cell))
|
||||
row := fromRow - 1
|
||||
col := TitleToNumber(fromCol)
|
||||
col, row := MustCellNameToCoordinates(cell)
|
||||
width = int(float64(width) * formatSet.XScale)
|
||||
height = int(float64(height) * formatSet.YScale)
|
||||
colStart, rowStart, _, _, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, col, row, formatSet.OffsetX, formatSet.OffsetY, width, height)
|
||||
colStart, rowStart, _, _, colEnd, rowEnd, x2, y2 :=
|
||||
f.positionObjectPixels(sheet, col, row, formatSet.OffsetX, formatSet.OffsetY, width, height)
|
||||
content, cNvPrID := f.drawingParser(drawingXML)
|
||||
twoCellAnchor := xdrCellAnchor{}
|
||||
twoCellAnchor.EditAs = formatSet.Positioning
|
||||
|
@ -471,30 +468,36 @@ func (f *File) getSheetRelationshipsTargetByID(sheet, rID string) string {
|
|||
// }
|
||||
//
|
||||
func (f *File) GetPicture(sheet, cell string) (string, []byte) {
|
||||
col, row := MustCellNameToCoordinates(cell)
|
||||
|
||||
xlsx := f.workSheetReader(sheet)
|
||||
if xlsx.Drawing == nil {
|
||||
return "", []byte{}
|
||||
}
|
||||
|
||||
target := f.getSheetRelationshipsTargetByID(sheet, xlsx.Drawing.RID)
|
||||
drawingXML := strings.Replace(target, "..", "xl", -1)
|
||||
cell = strings.ToUpper(cell)
|
||||
fromCol := string(strings.Map(letterOnlyMapF, cell))
|
||||
fromRow, _ := strconv.Atoi(strings.Map(intOnlyMapF, cell))
|
||||
row := fromRow - 1
|
||||
col := TitleToNumber(fromCol)
|
||||
drawingRelationships := strings.Replace(strings.Replace(target, "../drawings", "xl/drawings/_rels", -1), ".xml", ".xml.rels", -1)
|
||||
|
||||
drawingRelationships := strings.Replace(
|
||||
strings.Replace(target, "../drawings", "xl/drawings/_rels", -1), ".xml", ".xml.rels", -1)
|
||||
|
||||
wsDr, _ := f.drawingParser(drawingXML)
|
||||
|
||||
for _, anchor := range wsDr.TwoCellAnchor {
|
||||
if anchor.From != nil && anchor.Pic != nil {
|
||||
if anchor.From.Col == col && anchor.From.Row == row {
|
||||
xlsxWorkbookRelation := f.getDrawingRelationships(drawingRelationships, anchor.Pic.BlipFill.Blip.Embed)
|
||||
xlsxWorkbookRelation := f.getDrawingRelationships(drawingRelationships,
|
||||
anchor.Pic.BlipFill.Blip.Embed)
|
||||
_, ok := supportImageTypes[filepath.Ext(xlsxWorkbookRelation.Target)]
|
||||
if ok {
|
||||
return filepath.Base(xlsxWorkbookRelation.Target), []byte(f.XLSX[strings.Replace(xlsxWorkbookRelation.Target, "..", "xl", -1)])
|
||||
return filepath.Base(xlsxWorkbookRelation.Target),
|
||||
[]byte(f.XLSX[strings.Replace(xlsxWorkbookRelation.Target,
|
||||
"..", "xl", -1)])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_, ok := f.XLSX[drawingXML]
|
||||
if !ok {
|
||||
return "", nil
|
||||
|
|
195
rows.go
195
rows.go
|
@ -16,7 +16,6 @@ import (
|
|||
"io"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GetRows return all the rows in a sheet by given worksheet name (case
|
||||
|
@ -30,24 +29,30 @@ import (
|
|||
// }
|
||||
//
|
||||
func (f *File) GetRows(sheet string) [][]string {
|
||||
xlsx := f.workSheetReader(sheet)
|
||||
name, ok := f.sheetMap[trimSheetName(sheet)]
|
||||
if !ok {
|
||||
return [][]string{}
|
||||
return nil
|
||||
}
|
||||
|
||||
xlsx := f.workSheetReader(sheet)
|
||||
if xlsx != nil {
|
||||
output, _ := xml.Marshal(f.Sheet[name])
|
||||
f.saveFileList(name, replaceWorkSheetsRelationshipsNameSpaceBytes(output))
|
||||
}
|
||||
|
||||
xml.NewDecoder(bytes.NewReader(f.readXML(name)))
|
||||
d := f.sharedStringsReader()
|
||||
var inElement string
|
||||
var r xlsxRow
|
||||
tr, tc := f.getTotalRowsCols(name)
|
||||
rows := make([][]string, tr)
|
||||
var (
|
||||
inElement string
|
||||
rowData xlsxRow
|
||||
)
|
||||
|
||||
rowCount, colCount := f.getTotalRowsCols(name)
|
||||
rows := make([][]string, rowCount)
|
||||
for i := range rows {
|
||||
rows[i] = make([]string, tc+1)
|
||||
rows[i] = make([]string, colCount+1)
|
||||
}
|
||||
|
||||
var row int
|
||||
decoder := xml.NewDecoder(bytes.NewReader(f.readXML(name)))
|
||||
for {
|
||||
|
@ -59,15 +64,15 @@ func (f *File) GetRows(sheet string) [][]string {
|
|||
case xml.StartElement:
|
||||
inElement = startElement.Name.Local
|
||||
if inElement == "row" {
|
||||
r = xlsxRow{}
|
||||
_ = decoder.DecodeElement(&r, &startElement)
|
||||
cr := r.R - 1
|
||||
for _, colCell := range r.C {
|
||||
c := TitleToNumber(strings.Map(letterOnlyMapF, colCell.R))
|
||||
rowData = xlsxRow{}
|
||||
_ = decoder.DecodeElement(&rowData, &startElement)
|
||||
cr := rowData.R - 1
|
||||
for _, colCell := range rowData.C {
|
||||
col, _ := MustCellNameToCoordinates(colCell.R)
|
||||
val, _ := colCell.getValueFrom(f, d)
|
||||
rows[cr][c] = val
|
||||
rows[cr][col-1] = val
|
||||
if val != "" {
|
||||
row = r.R
|
||||
row = rowData.R
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -120,13 +125,13 @@ func (rows *Rows) Columns() []string {
|
|||
r := xlsxRow{}
|
||||
_ = rows.decoder.DecodeElement(&r, &startElement)
|
||||
d := rows.f.sharedStringsReader()
|
||||
row := make([]string, len(r.C))
|
||||
columns := make([]string, len(r.C))
|
||||
for _, colCell := range r.C {
|
||||
c := TitleToNumber(strings.Map(letterOnlyMapF, colCell.R))
|
||||
col, _ := MustCellNameToCoordinates(colCell.R)
|
||||
val, _ := colCell.getValueFrom(rows.f, d)
|
||||
row[c] = val
|
||||
columns[col-1] = val
|
||||
}
|
||||
return row
|
||||
return columns
|
||||
}
|
||||
|
||||
// ErrSheetNotExist defines an error of sheet is not exist
|
||||
|
@ -184,7 +189,7 @@ func (f *File) getTotalRowsCols(name string) (int, int) {
|
|||
_ = decoder.DecodeElement(&r, &startElement)
|
||||
tr = r.R
|
||||
for _, colCell := range r.C {
|
||||
col := TitleToNumber(strings.Map(letterOnlyMapF, colCell.R))
|
||||
col, _ := MustCellNameToCoordinates(colCell.R)
|
||||
if col > tc {
|
||||
tc = col
|
||||
}
|
||||
|
@ -202,13 +207,15 @@ func (f *File) getTotalRowsCols(name string) (int, int) {
|
|||
// xlsx.SetRowHeight("Sheet1", 1, 50)
|
||||
//
|
||||
func (f *File) SetRowHeight(sheet string, row int, height float64) {
|
||||
xlsx := f.workSheetReader(sheet)
|
||||
if row < 1 {
|
||||
return
|
||||
panic(newInvalidRowNumberError(row)) // Fail fats to avoid possible future side effects!
|
||||
}
|
||||
cells := 0
|
||||
|
||||
xlsx := f.workSheetReader(sheet)
|
||||
|
||||
prepareSheetXML(xlsx, 0, row)
|
||||
|
||||
rowIdx := row - 1
|
||||
completeRow(xlsx, row, cells)
|
||||
xlsx.SheetData.Row[rowIdx].Ht = height
|
||||
xlsx.SheetData.Row[rowIdx].CustomHeight = true
|
||||
}
|
||||
|
@ -232,8 +239,12 @@ func (f *File) getRowHeight(sheet string, row int) int {
|
|||
// xlsx.GetRowHeight("Sheet1", 1)
|
||||
//
|
||||
func (f *File) GetRowHeight(sheet string, row int) float64 {
|
||||
if row < 1 {
|
||||
panic(newInvalidRowNumberError(row)) // Fail fats to avoid possible future side effects!
|
||||
}
|
||||
|
||||
xlsx := f.workSheetReader(sheet)
|
||||
if row < 1 || row > len(xlsx.SheetData.Row) {
|
||||
if row > len(xlsx.SheetData.Row) {
|
||||
return defaultRowHeightPixels // it will be better to use 0, but we take care with BC
|
||||
}
|
||||
for _, v := range xlsx.SheetData.Row {
|
||||
|
@ -291,18 +302,13 @@ func (xlsx *xlsxC) getValueFrom(f *File, d *xlsxSST) (string, error) {
|
|||
// xlsx.SetRowVisible("Sheet1", 2, false)
|
||||
//
|
||||
func (f *File) SetRowVisible(sheet string, row int, visible bool) {
|
||||
xlsx := f.workSheetReader(sheet)
|
||||
if row < 1 {
|
||||
return
|
||||
panic(newInvalidRowNumberError(row)) // Fail fats to avoid possible future side effects!
|
||||
}
|
||||
cells := 0
|
||||
completeRow(xlsx, row, cells)
|
||||
rowIdx := row - 1
|
||||
if visible {
|
||||
xlsx.SheetData.Row[rowIdx].Hidden = false
|
||||
return
|
||||
}
|
||||
xlsx.SheetData.Row[rowIdx].Hidden = true
|
||||
|
||||
xlsx := f.workSheetReader(sheet)
|
||||
prepareSheetXML(xlsx, 0, row)
|
||||
xlsx.SheetData.Row[row-1].Hidden = !visible
|
||||
}
|
||||
|
||||
// GetRowVisible provides a function to get visible of a single row by given
|
||||
|
@ -312,14 +318,15 @@ func (f *File) SetRowVisible(sheet string, row int, visible bool) {
|
|||
// xlsx.GetRowVisible("Sheet1", 2)
|
||||
//
|
||||
func (f *File) GetRowVisible(sheet string, row int) bool {
|
||||
if row < 1 {
|
||||
panic(newInvalidRowNumberError(row)) // Fail fats to avoid possible future side effects!
|
||||
}
|
||||
|
||||
xlsx := f.workSheetReader(sheet)
|
||||
if row < 1 || row > len(xlsx.SheetData.Row) {
|
||||
if row > len(xlsx.SheetData.Row) {
|
||||
return false
|
||||
}
|
||||
rowIndex := row - 1
|
||||
cells := 0
|
||||
completeRow(xlsx, row, cells)
|
||||
return !xlsx.SheetData.Row[rowIndex].Hidden
|
||||
return !xlsx.SheetData.Row[row-1].Hidden
|
||||
}
|
||||
|
||||
// SetRowOutlineLevel provides a function to set outline level number of a
|
||||
|
@ -329,12 +336,11 @@ func (f *File) GetRowVisible(sheet string, row int) bool {
|
|||
// xlsx.SetRowOutlineLevel("Sheet1", 2, 1)
|
||||
//
|
||||
func (f *File) SetRowOutlineLevel(sheet string, row int, level uint8) {
|
||||
xlsx := f.workSheetReader(sheet)
|
||||
if row < 1 {
|
||||
return
|
||||
panic(newInvalidRowNumberError(row)) // Fail fats to avoid possible future side effects!
|
||||
}
|
||||
cells := 0
|
||||
completeRow(xlsx, row, cells)
|
||||
xlsx := f.workSheetReader(sheet)
|
||||
prepareSheetXML(xlsx, 0, row)
|
||||
xlsx.SheetData.Row[row-1].OutlineLevel = level
|
||||
}
|
||||
|
||||
|
@ -345,8 +351,11 @@ func (f *File) SetRowOutlineLevel(sheet string, row int, level uint8) {
|
|||
// xlsx.GetRowOutlineLevel("Sheet1", 2)
|
||||
//
|
||||
func (f *File) GetRowOutlineLevel(sheet string, row int) uint8 {
|
||||
if row < 1 {
|
||||
panic(newInvalidRowNumberError(row)) // Fail fats to avoid possible future side effects!
|
||||
}
|
||||
xlsx := f.workSheetReader(sheet)
|
||||
if row < 1 || row > len(xlsx.SheetData.Row) {
|
||||
if row > len(xlsx.SheetData.Row) {
|
||||
return 0
|
||||
}
|
||||
return xlsx.SheetData.Row[row-1].OutlineLevel
|
||||
|
@ -362,14 +371,18 @@ func (f *File) GetRowOutlineLevel(sheet string, row int) uint8 {
|
|||
// worksheet, it will cause a file error when you open it. The excelize only
|
||||
// partially updates these references currently.
|
||||
func (f *File) RemoveRow(sheet string, row int) {
|
||||
if row < 1 {
|
||||
panic(newInvalidRowNumberError(row)) // Fail fats to avoid possible future side effects!
|
||||
}
|
||||
|
||||
xlsx := f.workSheetReader(sheet)
|
||||
if row < 1 || row > len(xlsx.SheetData.Row) {
|
||||
if row > len(xlsx.SheetData.Row) {
|
||||
return
|
||||
}
|
||||
for i, r := range xlsx.SheetData.Row {
|
||||
if r.R == row {
|
||||
xlsx.SheetData.Row = append(xlsx.SheetData.Row[:i], xlsx.SheetData.Row[i+1:]...)
|
||||
f.adjustHelper(sheet, -1, row, -1)
|
||||
f.adjustHelper(sheet, rows, row, -1)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -383,9 +396,9 @@ func (f *File) RemoveRow(sheet string, row int) {
|
|||
//
|
||||
func (f *File) InsertRow(sheet string, row int) {
|
||||
if row < 1 {
|
||||
return
|
||||
panic(newInvalidRowNumberError(row)) // Fail fats to avoid possible future side effects!
|
||||
}
|
||||
f.adjustHelper(sheet, -1, row, 1)
|
||||
f.adjustHelper(sheet, rows, row, 1)
|
||||
}
|
||||
|
||||
// DuplicateRow inserts a copy of specified row (by it Excel row number) below
|
||||
|
@ -410,9 +423,12 @@ func (f *File) DuplicateRow(sheet string, row int) {
|
|||
// worksheet, it will cause a file error when you open it. The excelize only
|
||||
// partially updates these references currently.
|
||||
func (f *File) DuplicateRowTo(sheet string, row, row2 int) {
|
||||
xlsx := f.workSheetReader(sheet)
|
||||
if row < 1 {
|
||||
panic(newInvalidRowNumberError(row)) // Fail fats to avoid possible future side effects!
|
||||
}
|
||||
|
||||
if row < 1 || row > len(xlsx.SheetData.Row) || row2 < 1 || row == row2 {
|
||||
xlsx := f.workSheetReader(sheet)
|
||||
if row > len(xlsx.SheetData.Row) || row2 < 1 || row == row2 {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -430,7 +446,7 @@ func (f *File) DuplicateRowTo(sheet string, row, row2 int) {
|
|||
return
|
||||
}
|
||||
|
||||
f.adjustHelper(sheet, -1, row2, 1)
|
||||
f.adjustHelper(sheet, rows, row2, 1)
|
||||
|
||||
idx2 := -1
|
||||
for i, r := range xlsx.SheetData.Row {
|
||||
|
@ -478,62 +494,31 @@ func (f *File) DuplicateRowTo(sheet string, row, row2 int) {
|
|||
// 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 := range xlsx.SheetData.Row {
|
||||
lenCol := len(xlsx.SheetData.Row[k].C)
|
||||
if lenCol > 0 {
|
||||
endR := string(strings.Map(letterOnlyMapF, xlsx.SheetData.Row[k].C[lenCol-1].R))
|
||||
endRow, _ := strconv.Atoi(strings.Map(intOnlyMapF, xlsx.SheetData.Row[k].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]
|
||||
var 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for rowIdx := range xlsx.SheetData.Row {
|
||||
rowData := &xlsx.SheetData.Row[rowIdx]
|
||||
|
||||
// completeRow provides a 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
|
||||
colCount := len(rowData.C)
|
||||
if colCount == 0 {
|
||||
continue
|
||||
}
|
||||
lastCol, _ := MustCellNameToCoordinates(rowData.C[colCount-1].R)
|
||||
|
||||
if colCount < lastCol {
|
||||
oldList := rowData.C
|
||||
newlist := make([]xlsxC, 0, lastCol)
|
||||
|
||||
rowData.C = xlsx.SheetData.Row[rowIdx].C[:0]
|
||||
|
||||
for colIdx := 0; colIdx < lastCol; colIdx++ {
|
||||
newlist = append(newlist, xlsxC{R: MustCoordinatesToCellName(colIdx+1, rowIdx+1)})
|
||||
}
|
||||
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()
|
||||
|
||||
rowData.C = newlist
|
||||
|
||||
for colIdx := range oldList {
|
||||
colData := &oldList[colIdx]
|
||||
colNum, _ := MustCellNameToCoordinates(colData.R)
|
||||
xlsx.SheetData.Row[rowIdx].C[colNum-1] = *colData
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
134
rows_test.go
134
rows_test.go
|
@ -3,45 +3,39 @@ package excelize
|
|||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRows(t *testing.T) {
|
||||
const sheet2 = "Sheet2"
|
||||
|
||||
xlsx, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
rows, err := xlsx.Rows("Sheet2")
|
||||
rows, err := xlsx.Rows(sheet2)
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
rowStrs := make([][]string, 0)
|
||||
var i = 0
|
||||
collectedRows := make([][]string, 0)
|
||||
for rows.Next() {
|
||||
i++
|
||||
columns := rows.Columns()
|
||||
rowStrs = append(rowStrs, columns)
|
||||
collectedRows = append(collectedRows, trimSliceSpace(rows.Columns()))
|
||||
}
|
||||
|
||||
if !assert.NoError(t, rows.Error()) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
dstRows := xlsx.GetRows("Sheet2")
|
||||
if !assert.Equal(t, len(rowStrs), len(dstRows)) {
|
||||
t.FailNow()
|
||||
returnedRows := xlsx.GetRows(sheet2)
|
||||
for i := range returnedRows {
|
||||
returnedRows[i] = trimSliceSpace(returnedRows[i])
|
||||
}
|
||||
|
||||
for i := 0; i < len(rowStrs); i++ {
|
||||
if !assert.Equal(t, trimSliceSpace(dstRows[i]), trimSliceSpace(rowStrs[i])) {
|
||||
if !assert.Equal(t, collectedRows, returnedRows) {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
r := Rows{}
|
||||
r.Columns()
|
||||
|
@ -60,8 +54,13 @@ func TestRowHeight(t *testing.T) {
|
|||
xlsx := NewFile()
|
||||
sheet1 := xlsx.GetSheetName(1)
|
||||
|
||||
xlsx.SetRowHeight(sheet1, 0, defaultRowHeightPixels+1.0) // should no effect
|
||||
assert.Equal(t, defaultRowHeightPixels, xlsx.GetRowHeight("Sheet1", 0))
|
||||
assert.Panics(t, func() {
|
||||
xlsx.SetRowHeight(sheet1, 0, defaultRowHeightPixels+1.0)
|
||||
})
|
||||
|
||||
assert.Panics(t, func() {
|
||||
xlsx.GetRowHeight("Sheet1", 0)
|
||||
})
|
||||
|
||||
xlsx.SetRowHeight(sheet1, 1, 111.0)
|
||||
assert.Equal(t, 111.0, xlsx.GetRowHeight(sheet1, 1))
|
||||
|
@ -77,32 +76,47 @@ func TestRowHeight(t *testing.T) {
|
|||
convertColWidthToPixels(0)
|
||||
}
|
||||
|
||||
func TestRowVisibility(t *testing.T) {
|
||||
xlsx, err := prepareTestBook1()
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
xlsx.SetRowVisible("Sheet3", 2, false)
|
||||
xlsx.SetRowVisible("Sheet3", 2, true)
|
||||
xlsx.GetRowVisible("Sheet3", 2)
|
||||
|
||||
assert.Panics(t, func() {
|
||||
xlsx.SetRowVisible("Sheet3", 0, true)
|
||||
})
|
||||
|
||||
assert.Panics(t, func() {
|
||||
xlsx.GetRowVisible("Sheet3", 0)
|
||||
})
|
||||
|
||||
assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestRowVisibility.xlsx")))
|
||||
}
|
||||
|
||||
func TestRemoveRow(t *testing.T) {
|
||||
xlsx := NewFile()
|
||||
sheet1 := xlsx.GetSheetName(1)
|
||||
r := xlsx.workSheetReader(sheet1)
|
||||
|
||||
const (
|
||||
cellCount = 10
|
||||
colCount = 10
|
||||
rowCount = 10
|
||||
)
|
||||
for j := 1; j <= cellCount; j++ {
|
||||
for i := 1; i <= rowCount; i++ {
|
||||
axis := ToAlphaString(i) + strconv.Itoa(j)
|
||||
xlsx.SetCellStr(sheet1, axis, axis)
|
||||
}
|
||||
}
|
||||
fillCells(xlsx, sheet1, colCount, rowCount)
|
||||
|
||||
xlsx.SetCellHyperLink(sheet1, "A5", "https://github.com/360EntSecGroup-Skylar/excelize", "External")
|
||||
|
||||
assert.Panics(t, func() {
|
||||
xlsx.RemoveRow(sheet1, -1)
|
||||
if !assert.Len(t, r.SheetData.Row, rowCount) {
|
||||
t.FailNow()
|
||||
}
|
||||
})
|
||||
|
||||
assert.Panics(t, func() {
|
||||
xlsx.RemoveRow(sheet1, 0)
|
||||
if !assert.Len(t, r.SheetData.Row, rowCount) {
|
||||
t.FailNow()
|
||||
}
|
||||
})
|
||||
|
||||
xlsx.RemoveRow(sheet1, 4)
|
||||
if !assert.Len(t, r.SheetData.Row, rowCount-1) {
|
||||
|
@ -150,26 +164,20 @@ func TestInsertRow(t *testing.T) {
|
|||
r := xlsx.workSheetReader(sheet1)
|
||||
|
||||
const (
|
||||
cellCount = 10
|
||||
colCount = 10
|
||||
rowCount = 10
|
||||
)
|
||||
for j := 1; j <= cellCount; j++ {
|
||||
for i := 1; i < rowCount; i++ {
|
||||
axis := ToAlphaString(i) + strconv.Itoa(j)
|
||||
xlsx.SetCellStr(sheet1, axis, axis)
|
||||
}
|
||||
}
|
||||
fillCells(xlsx, sheet1, colCount, rowCount)
|
||||
|
||||
xlsx.SetCellHyperLink(sheet1, "A5", "https://github.com/360EntSecGroup-Skylar/excelize", "External")
|
||||
|
||||
assert.Panics(t, func() {
|
||||
xlsx.InsertRow(sheet1, -1)
|
||||
if !assert.Len(t, r.SheetData.Row, rowCount) {
|
||||
t.FailNow()
|
||||
}
|
||||
})
|
||||
|
||||
assert.Panics(t, func() {
|
||||
xlsx.InsertRow(sheet1, 0)
|
||||
if !assert.Len(t, r.SheetData.Row, rowCount) {
|
||||
t.FailNow()
|
||||
}
|
||||
})
|
||||
|
||||
xlsx.InsertRow(sheet1, 1)
|
||||
if !assert.Len(t, r.SheetData.Row, rowCount+1) {
|
||||
|
@ -304,19 +312,24 @@ func TestDuplicateRow(t *testing.T) {
|
|||
t.Run("ZeroWithNoRows", func(t *testing.T) {
|
||||
xlsx := NewFile()
|
||||
|
||||
assert.Panics(t, func() {
|
||||
xlsx.DuplicateRow(sheet, 0)
|
||||
})
|
||||
|
||||
if !assert.NoError(t, xlsx.SaveAs(fmt.Sprintf(outFile, "TestDuplicateRow.ZeroWithNoRows"))) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
assert.Equal(t, "", xlsx.GetCellValue(sheet, "A1"))
|
||||
assert.Equal(t, "", xlsx.GetCellValue(sheet, "B1"))
|
||||
assert.Equal(t, "", xlsx.GetCellValue(sheet, "A2"))
|
||||
assert.Equal(t, "", xlsx.GetCellValue(sheet, "B2"))
|
||||
|
||||
expect := map[string]string{
|
||||
"A1": "", "B1": "",
|
||||
"A2": "", "B2": "",
|
||||
}
|
||||
|
||||
for cell, val := range expect {
|
||||
if !assert.Equal(t, val, xlsx.GetCellValue(sheet, cell), cell) {
|
||||
t.FailNow()
|
||||
|
@ -444,31 +457,19 @@ func TestDuplicateRowInvalidRownum(t *testing.T) {
|
|||
"B3": "B3 Value",
|
||||
}
|
||||
|
||||
testRows := []int{-2, -1}
|
||||
invalidIndexes := []int{-100, -2, -1, 0}
|
||||
|
||||
testRowPairs := []struct {
|
||||
row1 int
|
||||
row2 int
|
||||
}{
|
||||
{-1, -1},
|
||||
{-1, 0},
|
||||
{-1, 1},
|
||||
{0, -1},
|
||||
{0, 0},
|
||||
{0, 1},
|
||||
{1, -1},
|
||||
{1, 1},
|
||||
{1, 0},
|
||||
}
|
||||
|
||||
for i, row := range testRows {
|
||||
name := fmt.Sprintf("TestRow_%d", i+1)
|
||||
for _, row := range invalidIndexes {
|
||||
name := fmt.Sprintf("%d", row)
|
||||
t.Run(name, func(t *testing.T) {
|
||||
xlsx := NewFile()
|
||||
for col, val := range cells {
|
||||
xlsx.SetCellStr(sheet, col, val)
|
||||
}
|
||||
|
||||
assert.Panics(t, func() {
|
||||
xlsx.DuplicateRow(sheet, row)
|
||||
})
|
||||
|
||||
for col, val := range cells {
|
||||
if !assert.Equal(t, val, xlsx.GetCellValue(sheet, col)) {
|
||||
|
@ -479,14 +480,18 @@ func TestDuplicateRowInvalidRownum(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
for i, pair := range testRowPairs {
|
||||
name := fmt.Sprintf("TestRowPair_%d", i+1)
|
||||
for _, row1 := range invalidIndexes {
|
||||
for _, row2 := range invalidIndexes {
|
||||
name := fmt.Sprintf("[%d,%d]", row1, row2)
|
||||
t.Run(name, func(t *testing.T) {
|
||||
xlsx := NewFile()
|
||||
for col, val := range cells {
|
||||
xlsx.SetCellStr(sheet, col, val)
|
||||
}
|
||||
xlsx.DuplicateRowTo(sheet, pair.row1, pair.row2)
|
||||
|
||||
assert.Panics(t, func() {
|
||||
xlsx.DuplicateRowTo(sheet, row1, row2)
|
||||
})
|
||||
|
||||
for col, val := range cells {
|
||||
if !assert.Equal(t, val, xlsx.GetCellValue(sheet, col)) {
|
||||
|
@ -496,6 +501,7 @@ func TestDuplicateRowInvalidRownum(t *testing.T) {
|
|||
assert.NoError(t, xlsx.SaveAs(fmt.Sprintf(outFile, name)))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func trimSliceSpace(s []string) []string {
|
||||
|
|
36
shape.go
36
shape.go
|
@ -283,15 +283,37 @@ func (f *File) AddShape(sheet, cell, format string) error {
|
|||
// addDrawingShape provides a function to add preset geometry by given sheet,
|
||||
// drawingXMLand format sets.
|
||||
func (f *File) addDrawingShape(sheet, drawingXML, cell string, formatSet *formatShape) {
|
||||
textUnderlineType := map[string]bool{"none": true, "words": true, "sng": true, "dbl": true, "heavy": true, "dotted": true, "dottedHeavy": true, "dash": true, "dashHeavy": true, "dashLong": true, "dashLongHeavy": true, "dotDash": true, "dotDashHeavy": true, "dotDotDash": true, "dotDotDashHeavy": true, "wavy": true, "wavyHeavy": true, "wavyDbl": true}
|
||||
cell = strings.ToUpper(cell)
|
||||
fromCol := string(strings.Map(letterOnlyMapF, cell))
|
||||
fromRow, _ := strconv.Atoi(strings.Map(intOnlyMapF, cell))
|
||||
row := fromRow - 1
|
||||
col := TitleToNumber(fromCol)
|
||||
fromCol, fromRow := MustCellNameToCoordinates(cell)
|
||||
colIdx := fromCol - 1
|
||||
rowIdx := fromRow - 1
|
||||
|
||||
textUnderlineType := map[string]bool{
|
||||
"none": true,
|
||||
"words": true,
|
||||
"sng": true,
|
||||
"dbl": true,
|
||||
"heavy": true,
|
||||
"dotted": true,
|
||||
"dottedHeavy": true,
|
||||
"dash": true,
|
||||
"dashHeavy": true,
|
||||
"dashLong": true,
|
||||
"dashLongHeavy": true,
|
||||
"dotDash": true,
|
||||
"dotDashHeavy": true,
|
||||
"dotDotDash": true,
|
||||
"dotDotDashHeavy": true,
|
||||
"wavy": true,
|
||||
"wavyHeavy": true,
|
||||
"wavyDbl": true,
|
||||
}
|
||||
|
||||
width := int(float64(formatSet.Width) * formatSet.Format.XScale)
|
||||
height := int(float64(formatSet.Height) * formatSet.Format.YScale)
|
||||
colStart, rowStart, _, _, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, col, row, formatSet.Format.OffsetX, formatSet.Format.OffsetY, width, height)
|
||||
|
||||
colStart, rowStart, _, _, colEnd, rowEnd, x2, y2 :=
|
||||
f.positionObjectPixels(sheet, colIdx, rowIdx, formatSet.Format.OffsetX, formatSet.Format.OffsetY,
|
||||
width, height)
|
||||
content, cNvPrID := f.drawingParser(drawingXML)
|
||||
twoCellAnchor := xdrCellAnchor{}
|
||||
twoCellAnchor.EditAs = formatSet.Format.Positioning
|
||||
|
|
46
sheet.go
46
sheet.go
|
@ -14,7 +14,6 @@ import (
|
|||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
|
@ -100,16 +99,16 @@ func (f *File) workBookWriter() {
|
|||
// workSheetWriter provides a function to save xl/worksheets/sheet%d.xml after
|
||||
// serialize structure.
|
||||
func (f *File) workSheetWriter() {
|
||||
for path, sheet := range f.Sheet {
|
||||
for p, sheet := range f.Sheet {
|
||||
if sheet != nil {
|
||||
for k, v := range sheet.SheetData.Row {
|
||||
f.Sheet[path].SheetData.Row[k].C = trimCell(v.C)
|
||||
f.Sheet[p].SheetData.Row[k].C = trimCell(v.C)
|
||||
}
|
||||
output, _ := xml.Marshal(sheet)
|
||||
f.saveFileList(path, replaceWorkSheetsRelationshipsNameSpaceBytes(output))
|
||||
ok := f.checked[path]
|
||||
f.saveFileList(p, replaceWorkSheetsRelationshipsNameSpaceBytes(output))
|
||||
ok := f.checked[p]
|
||||
if ok {
|
||||
f.checked[path] = false
|
||||
f.checked[p] = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -679,7 +678,9 @@ func (f *File) SearchSheet(sheet, value string, reg ...bool) []string {
|
|||
regSearch = r
|
||||
}
|
||||
xlsx := f.workSheetReader(sheet)
|
||||
result := []string{}
|
||||
var (
|
||||
result []string
|
||||
)
|
||||
name, ok := f.sheetMap[trimSheetName(sheet)]
|
||||
if !ok {
|
||||
return result
|
||||
|
@ -716,7 +717,9 @@ func (f *File) SearchSheet(sheet, value string, reg ...bool) []string {
|
|||
continue
|
||||
}
|
||||
}
|
||||
result = append(result, fmt.Sprintf("%s%d", strings.Map(letterOnlyMapF, colCell.R), r.R))
|
||||
|
||||
cellCol, _ := MustCellNameToCoordinates(colCell.R)
|
||||
result = append(result, MustCoordinatesToCellName(cellCol, r.R))
|
||||
}
|
||||
}
|
||||
default:
|
||||
|
@ -775,7 +778,7 @@ func (f *File) UnprotectSheet(sheet string) {
|
|||
// trimSheetName provides a function to trim invaild characters by given worksheet
|
||||
// name.
|
||||
func trimSheetName(name string) string {
|
||||
r := []rune{}
|
||||
var r []rune
|
||||
for _, v := range name {
|
||||
switch v {
|
||||
case 58, 92, 47, 63, 42, 91, 93: // replace :\/?*[]
|
||||
|
@ -1021,10 +1024,31 @@ func (f *File) workSheetRelsReader(path string) *xlsxWorkbookRels {
|
|||
// workSheetRelsWriter provides a function to save
|
||||
// xl/worksheets/_rels/sheet%d.xml.rels after serialize structure.
|
||||
func (f *File) workSheetRelsWriter() {
|
||||
for path, r := range f.WorkSheetRels {
|
||||
for p, r := range f.WorkSheetRels {
|
||||
if r != nil {
|
||||
v, _ := xml.Marshal(r)
|
||||
f.saveFileList(path, v)
|
||||
f.saveFileList(p, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fillSheetData fill missing row and cell XML data to made it continous from first cell [1, 1] to last cell [col, row]
|
||||
func prepareSheetXML(xlsx *xlsxWorksheet, col int, row int) {
|
||||
rowCount := len(xlsx.SheetData.Row)
|
||||
if rowCount < row {
|
||||
// append missing rows
|
||||
for rowIdx := rowCount; rowIdx < row; rowIdx++ {
|
||||
xlsx.SheetData.Row = append(xlsx.SheetData.Row, xlsxRow{R: rowIdx + 1})
|
||||
}
|
||||
}
|
||||
for rowIdx := range xlsx.SheetData.Row {
|
||||
rowData := &xlsx.SheetData.Row[rowIdx] // take reference
|
||||
cellCount := len(rowData.C)
|
||||
if cellCount < col {
|
||||
for colIdx := cellCount; colIdx < col; colIdx++ {
|
||||
cellName, _ := CoordinatesToCellName(colIdx+1, rowIdx+1)
|
||||
rowData.C = append(rowData.C, xlsxC{R: cellName})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
54
styles.go
54
styles.go
|
@ -2263,6 +2263,14 @@ func setCellXfs(style *xlsxStyleSheet, fontID, numFmtID, fillID, borderID int, a
|
|||
return style.CellXfs.Count - 1
|
||||
}
|
||||
|
||||
// GetCellStyle provides a 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)
|
||||
cellData, col, _ := f.prepareCell(xlsx, sheet, axis)
|
||||
return f.prepareCellStyle(xlsx, col, cellData.S)
|
||||
}
|
||||
|
||||
// SetCellStyle provides a function to add style attribute for cells by given
|
||||
// worksheet name, coordinate area and style ID. Note that diagonalDown and
|
||||
// diagonalUp type border should be use same color in the same coordinate
|
||||
|
@ -2329,42 +2337,36 @@ func setCellXfs(style *xlsxStyleSheet, fontID, numFmtID, fillID, borderID int, a
|
|||
// xlsx.SetCellStyle("Sheet1", "H9", "H9", style)
|
||||
//
|
||||
func (f *File) SetCellStyle(sheet, hcell, vcell string, styleID int) {
|
||||
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, err := strconv.Atoi(strings.Map(intOnlyMapF, hcell))
|
||||
hcol, hrow, err := CellNameToCoordinates(hcell)
|
||||
if err != nil {
|
||||
return
|
||||
panic(err)
|
||||
}
|
||||
hyAxis := hrow - 1
|
||||
hxAxis := TitleToNumber(hcol)
|
||||
|
||||
vcol := string(strings.Map(letterOnlyMapF, vcell))
|
||||
vrow, err := strconv.Atoi(strings.Map(intOnlyMapF, vcell))
|
||||
vcol, vrow, err := CellNameToCoordinates(vcell)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
vyAxis := vrow - 1
|
||||
vxAxis := TitleToNumber(vcol)
|
||||
|
||||
// Correct the coordinate area, such correct C1:B3 to B1:C3.
|
||||
if vxAxis < hxAxis {
|
||||
vxAxis, hxAxis = hxAxis, vxAxis
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if vyAxis < hyAxis {
|
||||
vyAxis, hyAxis = hyAxis, vyAxis
|
||||
// Normalize the coordinate area, such correct C1:B3 to B1:C3.
|
||||
if vcol < hcol {
|
||||
vcol, hcol = hcol, vcol
|
||||
}
|
||||
|
||||
if vrow < hrow {
|
||||
vrow, hrow = hrow, vrow
|
||||
}
|
||||
|
||||
hcolIdx := hcol - 1
|
||||
hrowIdx := hrow - 1
|
||||
|
||||
vcolIdx := vcol - 1
|
||||
vrowIdx := vrow - 1
|
||||
|
||||
xlsx := f.workSheetReader(sheet)
|
||||
prepareSheetXML(xlsx, vcol, vrow)
|
||||
|
||||
completeRow(xlsx, vyAxis+1, vxAxis+1)
|
||||
completeCol(xlsx, vyAxis+1, vxAxis+1)
|
||||
|
||||
for r := hyAxis; r <= vyAxis; r++ {
|
||||
for k := hxAxis; k <= vxAxis; k++ {
|
||||
for r := hrowIdx; r <= vrowIdx; r++ {
|
||||
for k := hcolIdx; k <= vcolIdx; k++ {
|
||||
xlsx.SheetData.Row[r].C[k].S = styleID
|
||||
}
|
||||
}
|
||||
|
|
92
table.go
92
table.go
|
@ -55,31 +55,25 @@ func (f *File) AddTable(sheet, hcell, vcell, format string) error {
|
|||
if err != nil {
|
||||
return 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)
|
||||
hcol, hrow := MustCellNameToCoordinates(hcell)
|
||||
vcol, vrow := MustCellNameToCoordinates(vcell)
|
||||
|
||||
vcol := string(strings.Map(letterOnlyMapF, vcell))
|
||||
vrow, _ := strconv.Atoi(strings.Map(intOnlyMapF, vcell))
|
||||
vyAxis := vrow - 1
|
||||
vxAxis := TitleToNumber(vcol)
|
||||
if vxAxis < hxAxis {
|
||||
vxAxis, hxAxis = hxAxis, vxAxis
|
||||
if vcol < hcol {
|
||||
vcol, hcol = hcol, vcol
|
||||
}
|
||||
if vyAxis < hyAxis {
|
||||
vyAxis, hyAxis = hyAxis, vyAxis
|
||||
|
||||
if vrow < hrow {
|
||||
vrow, hrow = hrow, vrow
|
||||
}
|
||||
|
||||
tableID := f.countTables() + 1
|
||||
sheetRelationshipsTableXML := "../tables/table" + strconv.Itoa(tableID) + ".xml"
|
||||
tableXML := strings.Replace(sheetRelationshipsTableXML, "..", "xl", -1)
|
||||
// Add first table for given sheet.
|
||||
rID := f.addSheetRelationships(sheet, SourceRelationshipTable, sheetRelationshipsTableXML, "")
|
||||
f.addSheetTable(sheet, rID)
|
||||
f.addTable(sheet, tableXML, hxAxis, hyAxis, vxAxis, vyAxis, tableID, formatSet)
|
||||
f.addTable(sheet, tableXML, hcol, hrow, vcol, vrow, tableID, formatSet)
|
||||
f.addContentTypePart(tableID, "table")
|
||||
return err
|
||||
}
|
||||
|
@ -112,18 +106,23 @@ func (f *File) addSheetTable(sheet string, rID int) {
|
|||
|
||||
// addTable provides a function to add table by given worksheet name,
|
||||
// coordinate area and format set.
|
||||
func (f *File) addTable(sheet, tableXML string, hxAxis, hyAxis, vxAxis, vyAxis, i int, formatSet *formatTable) {
|
||||
func (f *File) addTable(sheet, tableXML string, hcol, hrow, vcol, vrow, i int, formatSet *formatTable) {
|
||||
// Correct the minimum number of rows, the table at least two lines.
|
||||
if hyAxis == vyAxis {
|
||||
vyAxis++
|
||||
if hrow == vrow {
|
||||
vrow++
|
||||
}
|
||||
|
||||
// Correct table reference coordinate area, such correct C1:B3 to B1:C3.
|
||||
ref := ToAlphaString(hxAxis) + strconv.Itoa(hyAxis+1) + ":" + ToAlphaString(vxAxis) + strconv.Itoa(vyAxis+1)
|
||||
tableColumn := []*xlsxTableColumn{}
|
||||
ref := MustCoordinatesToCellName(hcol, hrow) + ":" + MustCoordinatesToCellName(vcol, vrow)
|
||||
|
||||
var (
|
||||
tableColumn []*xlsxTableColumn
|
||||
)
|
||||
|
||||
idx := 0
|
||||
for i := hxAxis; i <= vxAxis; i++ {
|
||||
for i := hcol; i <= vcol; i++ {
|
||||
idx++
|
||||
cell := ToAlphaString(i) + strconv.Itoa(hyAxis+1)
|
||||
cell := MustCoordinatesToCellName(i, hrow)
|
||||
name := f.GetCellValue(sheet, cell)
|
||||
if _, err := strconv.Atoi(name); err == nil {
|
||||
f.SetCellStr(sheet, cell, name)
|
||||
|
@ -245,37 +244,26 @@ func parseAutoFilterSet(formatSet string) (*formatAutoFilter, error) {
|
|||
// Price < 2000
|
||||
//
|
||||
func (f *File) AutoFilter(sheet, hcell, vcell, format string) error {
|
||||
hcol, hrow := MustCellNameToCoordinates(hcell)
|
||||
vcol, vrow := MustCellNameToCoordinates(vcell)
|
||||
|
||||
if vcol < hcol {
|
||||
vcol, hcol = hcol, vcol
|
||||
}
|
||||
|
||||
if vrow < hrow {
|
||||
vrow, hrow = hrow, vrow
|
||||
}
|
||||
|
||||
formatSet, _ := parseAutoFilterSet(format)
|
||||
|
||||
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 := string(strings.Map(letterOnlyMapF, vcell))
|
||||
vrow, _ := strconv.Atoi(strings.Map(intOnlyMapF, vcell))
|
||||
vyAxis := vrow - 1
|
||||
vxAxis := TitleToNumber(vcol)
|
||||
|
||||
if vxAxis < hxAxis {
|
||||
vxAxis, hxAxis = hxAxis, vxAxis
|
||||
}
|
||||
|
||||
if vyAxis < hyAxis {
|
||||
vyAxis, hyAxis = hyAxis, vyAxis
|
||||
}
|
||||
ref := ToAlphaString(hxAxis) + strconv.Itoa(hyAxis+1) + ":" + ToAlphaString(vxAxis) + strconv.Itoa(vyAxis+1)
|
||||
refRange := vxAxis - hxAxis
|
||||
return f.autoFilter(sheet, ref, refRange, hxAxis, formatSet)
|
||||
ref := MustCoordinatesToCellName(hcol, hrow) + ":" + MustCoordinatesToCellName(vcol, vrow)
|
||||
refRange := vcol - hcol
|
||||
return f.autoFilter(sheet, ref, refRange, hcol, formatSet)
|
||||
}
|
||||
|
||||
// autoFilter provides a function to extract the tokens from the filter
|
||||
// expression. The tokens are mainly non-whitespace groups.
|
||||
func (f *File) autoFilter(sheet, ref string, refRange, hxAxis int, formatSet *formatAutoFilter) error {
|
||||
func (f *File) autoFilter(sheet, ref string, refRange, col int, formatSet *formatAutoFilter) error {
|
||||
xlsx := f.workSheetReader(sheet)
|
||||
if xlsx.SheetPr != nil {
|
||||
xlsx.SheetPr.FilterMode = true
|
||||
|
@ -288,11 +276,13 @@ func (f *File) autoFilter(sheet, ref string, refRange, hxAxis int, formatSet *fo
|
|||
if formatSet.Column == "" || formatSet.Expression == "" {
|
||||
return nil
|
||||
}
|
||||
col := TitleToNumber(formatSet.Column)
|
||||
offset := col - hxAxis
|
||||
|
||||
fsCol := MustColumnNameToNumber(formatSet.Column)
|
||||
offset := fsCol - col
|
||||
if offset < 0 || offset > refRange {
|
||||
return fmt.Errorf("incorrect index of column '%s'", formatSet.Column)
|
||||
}
|
||||
|
||||
filter.FilterColumn = &xlsxFilterColumn{
|
||||
ColID: offset,
|
||||
}
|
||||
|
@ -315,7 +305,7 @@ func (f *File) autoFilter(sheet, ref string, refRange, hxAxis int, formatSet *fo
|
|||
func (f *File) writeAutoFilter(filter *xlsxAutoFilter, exp []int, tokens []string) {
|
||||
if len(exp) == 1 && exp[0] == 2 {
|
||||
// Single equality.
|
||||
filters := []*xlsxFilter{}
|
||||
var filters []*xlsxFilter
|
||||
filters = append(filters, &xlsxFilter{Val: tokens[0]})
|
||||
filter.FilterColumn.Filters = &xlsxFilters{Filter: filters}
|
||||
} else if len(exp) == 3 && exp[0] == 2 && exp[1] == 1 && exp[2] == 2 {
|
||||
|
|
Loading…
Reference in New Issue