2016-08-30 11:51:31 +08:00
package excelize
import (
"archive/zip"
2016-09-04 19:25:31 +08:00
"bytes"
2016-08-30 11:51:31 +08:00
"encoding/xml"
"strconv"
"strings"
)
2016-09-05 16:37:15 +08:00
// File define a populated xlsx.File struct.
type File struct {
2016-09-06 21:20:24 +08:00
XLSX map [ string ] string
Path string
SheetCount int
2016-08-30 11:51:31 +08:00
}
2016-09-02 11:54:52 +08:00
// OpenFile take the name of an XLSX file and returns a populated
2016-08-30 11:51:31 +08:00
// xlsx.File struct for it.
2016-09-06 21:20:24 +08:00
func OpenFile ( filename string ) ( * File , error ) {
2016-08-30 11:51:31 +08:00
var f * zip . ReadCloser
2016-09-06 21:20:24 +08:00
var err error
2016-09-05 16:37:15 +08:00
file := make ( map [ string ] string )
2016-09-06 21:20:24 +08:00
sheetCount := 0
f , err = zip . OpenReader ( filename )
if err != nil {
return & File { } , err
2016-08-30 11:51:31 +08:00
}
2016-09-07 20:09:02 +08:00
file , sheetCount , _ = ReadZip ( f )
2016-09-06 21:20:24 +08:00
return & File {
XLSX : file ,
Path : filename ,
SheetCount : sheetCount ,
} , nil
2016-08-30 11:51:31 +08:00
}
2017-01-17 19:06:42 +08:00
// SetCellValue provides function to set int or string type value of a cell.
2016-09-09 19:39:41 +08:00
func ( f * File ) SetCellValue ( sheet string , axis string , value interface { } ) {
switch t := value . ( type ) {
2016-11-24 11:42:51 +08:00
case int :
2016-09-09 19:39:41 +08:00
f . SetCellInt ( sheet , axis , value . ( int ) )
2016-11-24 11:42:51 +08:00
case int8 :
f . SetCellInt ( sheet , axis , int ( value . ( int8 ) ) )
case int16 :
f . SetCellInt ( sheet , axis , int ( value . ( int16 ) ) )
case int32 :
f . SetCellInt ( sheet , axis , int ( value . ( int32 ) ) )
case int64 :
f . SetCellInt ( sheet , axis , int ( value . ( int64 ) ) )
case float32 :
2017-01-12 02:26:06 +08:00
f . SetCellDefault ( sheet , axis , strconv . FormatFloat ( float64 ( value . ( float32 ) ) , 'f' , - 1 , 32 ) )
2016-11-24 11:42:51 +08:00
case float64 :
2017-01-12 02:26:06 +08:00
f . SetCellDefault ( sheet , axis , strconv . FormatFloat ( float64 ( value . ( float64 ) ) , 'f' , - 1 , 64 ) )
2016-09-09 19:39:41 +08:00
case string :
f . SetCellStr ( sheet , axis , t )
case [ ] byte :
f . SetCellStr ( sheet , axis , string ( t ) )
default :
2017-01-18 14:47:23 +08:00
f . SetCellStr ( sheet , axis , "" )
2016-09-09 19:39:41 +08:00
}
}
2017-01-17 19:06:42 +08:00
// SetCellInt provides function to set int type value of a cell.
2016-09-05 16:37:15 +08:00
func ( f * File ) SetCellInt ( sheet string , axis string , value int ) {
2016-08-30 11:51:31 +08:00
axis = strings . ToUpper ( axis )
var xlsx xlsxWorksheet
2016-09-12 17:37:06 +08:00
col := string ( strings . Map ( letterOnlyMapF , axis ) )
row , _ := strconv . Atoi ( strings . Map ( intOnlyMapF , axis ) )
2016-08-30 11:51:31 +08:00
xAxis := row - 1
yAxis := titleToNumber ( col )
2017-01-17 19:06:42 +08:00
name := "xl/worksheets/" + strings . ToLower ( sheet ) + ".xml"
2016-09-05 16:37:15 +08:00
xml . Unmarshal ( [ ] byte ( f . readXML ( name ) ) , & xlsx )
2016-08-30 11:51:31 +08:00
rows := xAxis + 1
cell := yAxis + 1
xlsx = completeRow ( xlsx , rows , cell )
xlsx = completeCol ( xlsx , rows , cell )
xlsx . SheetData . Row [ xAxis ] . C [ yAxis ] . T = ""
xlsx . SheetData . Row [ xAxis ] . C [ yAxis ] . V = strconv . Itoa ( value )
2016-09-09 19:39:41 +08:00
output , _ := xml . Marshal ( xlsx )
2016-12-31 23:47:30 +08:00
f . saveFileList ( name , replaceWorkSheetsRelationshipsNameSpace ( string ( output ) ) )
2016-08-30 11:51:31 +08:00
}
2017-01-17 19:06:42 +08:00
// SetCellStr provides function to set string type value of a cell.
2016-09-05 16:37:15 +08:00
func ( f * File ) SetCellStr ( sheet string , axis string , value string ) {
2016-08-30 11:51:31 +08:00
axis = strings . ToUpper ( axis )
var xlsx xlsxWorksheet
2016-09-12 17:37:06 +08:00
col := string ( strings . Map ( letterOnlyMapF , axis ) )
row , _ := strconv . Atoi ( strings . Map ( intOnlyMapF , axis ) )
2016-08-30 11:51:31 +08:00
xAxis := row - 1
yAxis := titleToNumber ( col )
2017-01-17 19:06:42 +08:00
name := "xl/worksheets/" + strings . ToLower ( sheet ) + ".xml"
2016-09-05 16:37:15 +08:00
xml . Unmarshal ( [ ] byte ( f . readXML ( name ) ) , & xlsx )
2016-08-30 11:51:31 +08:00
rows := xAxis + 1
cell := yAxis + 1
xlsx = completeRow ( xlsx , rows , cell )
xlsx = completeCol ( xlsx , rows , cell )
2017-01-17 19:06:42 +08:00
xlsx . SheetData . Row [ xAxis ] . C [ yAxis ] . T = "str"
2016-08-30 11:51:31 +08:00
xlsx . SheetData . Row [ xAxis ] . C [ yAxis ] . V = value
2016-09-09 19:39:41 +08:00
output , _ := xml . Marshal ( xlsx )
2016-12-31 23:47:30 +08:00
f . saveFileList ( name , replaceWorkSheetsRelationshipsNameSpace ( string ( output ) ) )
2016-08-30 11:51:31 +08:00
}
2017-01-17 19:06:42 +08:00
// SetCellDefault provides function to set string type value of a cell as default format without escaping the cell.
2017-01-12 02:26:06 +08:00
func ( f * File ) SetCellDefault ( sheet string , axis string , value string ) {
2017-01-17 19:06:42 +08:00
axis = strings . ToUpper ( axis )
var xlsx xlsxWorksheet
col := string ( strings . Map ( letterOnlyMapF , axis ) )
row , _ := strconv . Atoi ( strings . Map ( intOnlyMapF , axis ) )
xAxis := row - 1
yAxis := titleToNumber ( col )
2017-01-12 02:26:06 +08:00
2017-01-17 19:06:42 +08:00
name := "xl/worksheets/" + strings . ToLower ( sheet ) + ".xml"
xml . Unmarshal ( [ ] byte ( f . readXML ( name ) ) , & xlsx )
2017-01-12 02:26:06 +08:00
2017-01-17 19:06:42 +08:00
rows := xAxis + 1
cell := yAxis + 1
2017-01-12 02:26:06 +08:00
2017-01-17 19:06:42 +08:00
xlsx = completeRow ( xlsx , rows , cell )
xlsx = completeCol ( xlsx , rows , cell )
2017-01-12 02:26:06 +08:00
2017-01-17 19:06:42 +08:00
xlsx . SheetData . Row [ xAxis ] . C [ yAxis ] . T = ""
xlsx . SheetData . Row [ xAxis ] . C [ yAxis ] . V = value
2017-01-12 02:26:06 +08:00
2017-01-17 19:06:42 +08:00
output , _ := xml . Marshal ( xlsx )
f . saveFileList ( name , replaceWorkSheetsRelationshipsNameSpace ( string ( output ) ) )
2017-01-12 02:26:06 +08:00
}
2016-10-19 20:39:44 +08:00
// Completion column element tags of XML in a sheet.
2016-08-30 11:51:31 +08:00
func completeCol ( xlsx xlsxWorksheet , row int , cell int ) xlsxWorksheet {
if len ( xlsx . SheetData . Row ) < cell {
for i := len ( xlsx . SheetData . Row ) ; i < cell ; i ++ {
xlsx . SheetData . Row = append ( xlsx . SheetData . Row , xlsxRow {
R : i + 1 ,
} )
}
}
2016-09-04 19:25:31 +08:00
buffer := bytes . Buffer { }
2016-08-30 11:51:31 +08:00
for k , v := range xlsx . SheetData . Row {
if len ( v . C ) < cell {
start := len ( v . C )
for iii := start ; iii < cell ; iii ++ {
2016-09-04 19:25:31 +08:00
buffer . WriteString ( toAlphaString ( iii + 1 ) )
buffer . WriteString ( strconv . Itoa ( k + 1 ) )
2016-08-30 11:51:31 +08:00
xlsx . SheetData . Row [ k ] . C = append ( xlsx . SheetData . Row [ k ] . C , xlsxC {
2016-09-04 19:25:31 +08:00
R : buffer . String ( ) ,
2016-08-30 11:51:31 +08:00
} )
2016-09-04 19:25:31 +08:00
buffer . Reset ( )
2016-08-30 11:51:31 +08:00
}
}
}
return xlsx
}
2016-10-19 20:39:44 +08:00
// Completion row element tags of XML in a sheet.
2016-08-30 11:51:31 +08:00
func completeRow ( xlsx xlsxWorksheet , row int , cell int ) xlsxWorksheet {
2016-12-23 21:41:54 +08:00
currentRows := len ( xlsx . SheetData . Row )
if currentRows > 1 {
lastRow := xlsx . SheetData . Row [ currentRows - 1 ] . R
if lastRow >= row {
row = lastRow
}
2016-12-20 14:40:36 +08:00
}
sheetData := xlsxSheetData { }
existsRows := map [ int ] int { }
for k , v := range xlsx . SheetData . Row {
existsRows [ v . R ] = k
}
for i := 0 ; i < row ; i ++ {
_ , ok := existsRows [ i + 1 ]
if ok {
sheetData . Row = append ( sheetData . Row , xlsx . SheetData . Row [ existsRows [ i + 1 ] ] )
continue
2016-08-30 11:51:31 +08:00
}
2016-12-20 14:40:36 +08:00
sheetData . Row = append ( sheetData . Row , xlsxRow {
R : i + 1 ,
} )
}
buffer := bytes . Buffer { }
for ii := 0 ; ii < row ; ii ++ {
start := len ( sheetData . Row [ ii ] . C )
if start == 0 {
for iii := start ; iii < cell ; iii ++ {
buffer . WriteString ( toAlphaString ( iii + 1 ) )
buffer . WriteString ( strconv . Itoa ( ii + 1 ) )
sheetData . Row [ ii ] . C = append ( sheetData . Row [ ii ] . C , xlsxC {
R : buffer . String ( ) ,
} )
buffer . Reset ( )
2016-08-30 11:51:31 +08:00
}
}
}
2016-12-20 14:40:36 +08:00
xlsx . SheetData = sheetData
2016-08-30 11:51:31 +08:00
return xlsx
}
2016-10-19 20:39:44 +08:00
// Replace xl/worksheets/sheet%d.xml XML tags to self-closing for compatible Office Excel 2007.
2016-08-30 11:51:31 +08:00
func replaceWorkSheetsRelationshipsNameSpace ( workbookMarshal string ) string {
oldXmlns := ` <worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"> `
newXmlns := ` <worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:mx="http://schemas.microsoft.com/office/mac/excel/2008/main" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mv="urn:schemas-microsoft-com:mac:vml" xmlns:x14="http://schemas.microsoft.com/office/spreadsheetml/2009/9/main" xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac" xmlns:xm="http://schemas.microsoft.com/office/excel/2006/main"> `
workbookMarshal = strings . Replace ( workbookMarshal , oldXmlns , newXmlns , - 1 )
return workbookMarshal
}
// Check XML tags and fix discontinuous case, for example:
//
2016-09-09 19:39:41 +08:00
// <row r="15" spans="1:22" x14ac:dyDescent="0.2">
// <c r="A15" s="2" />
// <c r="B15" s="2" />
// <c r="F15" s="1" />
// <c r="G15" s="1" />
// </row>
2016-08-30 11:51:31 +08:00
//
// in this case, we should to change it to
//
2016-09-09 19:39:41 +08:00
// <row r="15" spans="1:22" x14ac:dyDescent="0.2">
// <c r="A15" s="2" />
// <c r="B15" s="2" />
// <c r="C15" s="2" />
// <c r="D15" s="2" />
// <c r="E15" s="2" />
// <c r="F15" s="1" />
// <c r="G15" s="1" />
// </row>
2016-08-30 11:51:31 +08:00
//
2016-09-06 21:20:24 +08:00
// Noteice: this method could be very slow for large spreadsheets (more than 3000 rows one sheet).
2016-08-30 11:51:31 +08:00
func checkRow ( xlsx xlsxWorksheet ) xlsxWorksheet {
2016-09-04 19:25:31 +08:00
buffer := bytes . Buffer { }
2016-08-30 11:51:31 +08:00
for k , v := range xlsx . SheetData . Row {
lenCol := len ( v . C )
2016-09-02 19:06:48 +08:00
if lenCol < 1 {
continue
}
2016-09-12 17:37:06 +08:00
endR := string ( strings . Map ( letterOnlyMapF , v . C [ lenCol - 1 ] . R ) )
endRow , _ := strconv . Atoi ( strings . Map ( intOnlyMapF , v . C [ lenCol - 1 ] . R ) )
2016-09-07 20:09:02 +08:00
endCol := titleToNumber ( endR ) + 1
2016-08-30 11:51:31 +08:00
if lenCol < endCol {
oldRow := xlsx . SheetData . Row [ k ] . C
xlsx . SheetData . Row [ k ] . C = xlsx . SheetData . Row [ k ] . C [ : 0 ]
tmp := [ ] xlsxC { }
for i := 0 ; i <= endCol ; i ++ {
2016-09-04 19:25:31 +08:00
buffer . WriteString ( toAlphaString ( i + 1 ) )
buffer . WriteString ( strconv . Itoa ( endRow ) )
2016-08-30 11:51:31 +08:00
tmp = append ( tmp , xlsxC {
2016-09-04 19:25:31 +08:00
R : buffer . String ( ) ,
2016-08-30 11:51:31 +08:00
} )
2016-09-04 19:25:31 +08:00
buffer . Reset ( )
2016-08-30 11:51:31 +08:00
}
xlsx . SheetData . Row [ k ] . C = tmp
for _ , y := range oldRow {
2016-09-12 17:37:06 +08:00
colAxis := titleToNumber ( string ( strings . Map ( letterOnlyMapF , y . R ) ) )
2016-08-30 11:51:31 +08:00
xlsx . SheetData . Row [ k ] . C [ colAxis ] = y
}
}
}
return xlsx
}
2016-09-06 21:20:24 +08:00
2016-09-07 20:09:02 +08:00
// UpdateLinkedValue fix linked values within a spreadsheet are not updating in
// Office Excel 2007 and 2010. This function will be remove value tag when met a
2016-09-12 17:37:06 +08:00
// cell have a linked value. Reference https://social.technet.microsoft.com/Forums/office/en-US/e16bae1f-6a2c-4325-8013-e989a3479066/excel-2010-linked-cells-not-updating?forum=excel
2016-09-06 21:20:24 +08:00
//
// Notice: after open XLSX file Excel will be update linked value and generate
// new value and will prompt save file or not.
//
// For example:
//
2016-09-09 19:39:41 +08:00
// <row r="19" spans="2:2">
// <c r="B19">
// <f>SUM(Sheet2!D2,Sheet2!D11)</f>
// <v>100</v>
// </c>
// </row>
2016-09-06 21:20:24 +08:00
//
// to
//
2016-09-09 19:39:41 +08:00
// <row r="19" spans="2:2">
// <c r="B19">
// <f>SUM(Sheet2!D2,Sheet2!D11)</f>
// </c>
// </row>
2017-01-17 19:06:42 +08:00
//
2016-09-06 21:20:24 +08:00
func ( f * File ) UpdateLinkedValue ( ) {
for i := 1 ; i <= f . SheetCount ; i ++ {
var xlsx xlsxWorksheet
2017-01-17 19:06:42 +08:00
name := "xl/worksheets/sheet" + strconv . Itoa ( i ) + ".xml"
2016-09-06 21:20:24 +08:00
xml . Unmarshal ( [ ] byte ( f . readXML ( name ) ) , & xlsx )
for indexR , row := range xlsx . SheetData . Row {
for indexC , col := range row . C {
2017-01-18 14:47:23 +08:00
if col . F != nil && col . V != "" {
2017-01-17 19:06:42 +08:00
xlsx . SheetData . Row [ indexR ] . C [ indexC ] . V = ""
xlsx . SheetData . Row [ indexR ] . C [ indexC ] . T = ""
2016-09-06 21:20:24 +08:00
}
}
}
output , _ := xml . Marshal ( xlsx )
2016-12-31 23:47:30 +08:00
f . saveFileList ( name , replaceWorkSheetsRelationshipsNameSpace ( string ( output ) ) )
2016-09-06 21:20:24 +08:00
}
}