diff --git a/README.md b/README.md index 357c92d..1cfe4e5 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ ## Introduction -Excelize is a library written in pure Golang and providing a set of functions that allow you to write to and read from XLSX files. Support reads and writes XLSX file generated by Office Excel 2007 and later. The full API docs can be seen using go's built-in documentation tool, or online at [godoc.org](https://godoc.org/github.com/Luxurioust/excelize). +Excelize is a library written in pure Golang and providing a set of functions that allow you to write to and read from XLSX files. Support reads and writes XLSX file generated by Office Excel 2007 and later. Support save file without losing original charts of XLSX. The full API docs can be seen using go's built-in documentation tool, or online at [godoc.org](https://godoc.org/github.com/Luxurioust/excelize). ## Basic Usage @@ -35,11 +35,11 @@ import ( func main() { xlsx := excelize.CreateFile() - xlsx = excelize.NewSheet(xlsx, 2, "Sheet2") - xlsx = excelize.NewSheet(xlsx, 3, "Sheet3") - xlsx = excelize.SetCellInt(xlsx, "Sheet2", "A23", 10) - xlsx = excelize.SetCellStr(xlsx, "Sheet3", "B20", "Hello") - err := excelize.Save(xlsx, "/home/Workbook.xlsx") + xlsx.NewSheet(2, "Sheet2") + xlsx.NewSheet(3, "Sheet3") + xlsx.SetCellInt("Sheet2", "A23", 10) + xlsx.SetCellStr("Sheet3", "B20", "Hello") + err := xlsx.WriteTo("/home/Workbook.xlsx") if err != nil { fmt.Println(err) } @@ -59,20 +59,17 @@ import ( ) func main() { - xlsx, err := excelize.OpenFile("/home/Workbook.xlsx") + xlsx := excelize.OpenFile("/home/Workbook.xlsx") + xlsx.SetCellInt("Sheet2", "B2", 100) + xlsx.SetCellStr("Sheet2", "C11", "Hello") + xlsx.NewSheet(3, "TestSheet") + xlsx.SetCellInt("Sheet3", "A23", 10) + xlsx.SetCellStr("Sheet3", "b230", "World") + xlsx.SetActiveSheet(2) + err := xlsx.Save() if err != nil { fmt.Println(err) } - xlsx = excelize.SetCellInt(xlsx, "Sheet2", "B2", 100) - xlsx = excelize.SetCellStr(xlsx, "Sheet2", "C11", "Hello") - xlsx = excelize.NewSheet(xlsx, 3, "TestSheet") - xlsx = excelize.SetCellInt(xlsx, "Sheet3", "A23", 10) - xlsx = excelize.SetCellStr(xlsx, "Sheet3", "b230", "World") - xlsx = excelize.SetActiveSheet(xlsx, 2) - err = excelize.Save(xlsx, "/home/Workbook.xlsx") - if err != nil { - fmt.Println(err) - } } ``` @@ -87,11 +84,8 @@ import ( ) func main() { - xlsx, err := excelize.OpenFile("/home/Workbook.xlsx") - if err != nil { - fmt.Println(err) - } - cell := excelize.GetCellValue(file, "Sheet2", "D11") + xlsx := excelize.OpenFile("/home/Workbook.xlsx") + cell := xlsx.GetCellValue("Sheet2", "D11") fmt.Println(cell) } ``` diff --git a/cell.go b/cell.go index ecfc5ca..4e84260 100644 --- a/cell.go +++ b/cell.go @@ -7,13 +7,13 @@ import ( ) // GetCellValue provide function get value from cell by given sheet index and axis in XLSX file -func GetCellValue(file map[string]string, sheet string, axis string) string { +func (f *File) GetCellValue(sheet string, axis string) string { axis = strings.ToUpper(axis) var xlsx xlsxWorksheet row := getRowIndex(axis) xAxis := row - 1 name := `xl/worksheets/` + strings.ToLower(sheet) + `.xml` - xml.Unmarshal([]byte(readXML(file, name)), &xlsx) + xml.Unmarshal([]byte(f.readXML(name)), &xlsx) rows := len(xlsx.SheetData.Row) if rows <= xAxis { return `` @@ -26,7 +26,7 @@ func GetCellValue(file map[string]string, sheet string, axis string) string { shardStrings := xlsxSST{} xlsxSI := 0 xlsxSI, _ = strconv.Atoi(v.V) - xml.Unmarshal([]byte(readXML(file, `xl/sharedStrings.xml`)), &shardStrings) + xml.Unmarshal([]byte(f.readXML(`xl/sharedStrings.xml`)), &shardStrings) return shardStrings.SI[xlsxSI].T case "str": return v.V diff --git a/excelize.go b/excelize.go index 2dd62d3..d37860f 100644 --- a/excelize.go +++ b/excelize.go @@ -9,26 +9,27 @@ import ( "strings" ) -// FileList define a populated xlsx.File struct. -type FileList struct { - Key string - Value string +// File define a populated xlsx.File struct. +type File struct { + XLSX map[string]string + Path string } // OpenFile take the name of an XLSX file and returns a populated // xlsx.File struct for it. -func OpenFile(filename string) (file map[string]string, err error) { +func OpenFile(filename string) *File { var f *zip.ReadCloser - f, err = zip.OpenReader(filename) - if err != nil { - return nil, err + file := make(map[string]string) + f, _ = zip.OpenReader(filename) + file, _ = ReadZip(f) + return &File{ + XLSX: file, + Path: filename, } - file, err = ReadZip(f) - return } // SetCellInt provide function to set int type value of a cell -func SetCellInt(file map[string]string, sheet string, axis string, value int) map[string]string { +func (f *File) SetCellInt(sheet string, axis string, value int) { axis = strings.ToUpper(axis) var xlsx xlsxWorksheet col := getColIndex(axis) @@ -37,7 +38,7 @@ func SetCellInt(file map[string]string, sheet string, axis string, value int) ma yAxis := titleToNumber(col) name := `xl/worksheets/` + strings.ToLower(sheet) + `.xml` - xml.Unmarshal([]byte(readXML(file, name)), &xlsx) + xml.Unmarshal([]byte(f.readXML(name)), &xlsx) rows := xAxis + 1 cell := yAxis + 1 @@ -53,12 +54,11 @@ func SetCellInt(file map[string]string, sheet string, axis string, value int) ma if err != nil { fmt.Println(err) } - saveFileList(file, name, replaceRelationshipsID(replaceWorkSheetsRelationshipsNameSpace(string(output)))) - return file + f.saveFileList(name, replaceRelationshipsID(replaceWorkSheetsRelationshipsNameSpace(string(output)))) } // SetCellStr provide function to set string type value of a cell -func SetCellStr(file map[string]string, sheet string, axis string, value string) map[string]string { +func (f *File) SetCellStr(sheet string, axis string, value string) { axis = strings.ToUpper(axis) var xlsx xlsxWorksheet col := getColIndex(axis) @@ -67,7 +67,7 @@ func SetCellStr(file map[string]string, sheet string, axis string, value string) yAxis := titleToNumber(col) name := `xl/worksheets/` + strings.ToLower(sheet) + `.xml` - xml.Unmarshal([]byte(readXML(file, name)), &xlsx) + xml.Unmarshal([]byte(f.readXML(name)), &xlsx) rows := xAxis + 1 cell := yAxis + 1 @@ -83,8 +83,7 @@ func SetCellStr(file map[string]string, sheet string, axis string, value string) if err != nil { fmt.Println(err) } - saveFileList(file, name, replaceRelationshipsID(replaceWorkSheetsRelationshipsNameSpace(string(output)))) - return file + f.saveFileList(name, replaceRelationshipsID(replaceWorkSheetsRelationshipsNameSpace(string(output)))) } // Completion column element tags of XML in a sheet diff --git a/excelize_test.go b/excelize_test.go index 3751775..d206e2d 100644 --- a/excelize_test.go +++ b/excelize_test.go @@ -7,57 +7,51 @@ import ( func TestExcelize(t *testing.T) { // Test update a XLSX file - file, err := OpenFile("./test/Workbook1.xlsx") - if err != nil { - t.Log(err) - } - file = SetCellInt(file, "SHEET2", "B2", 100) - file = SetCellStr(file, "SHEET2", "C11", "Knowns") - file = NewSheet(file, 3, "TestSheet") - file = SetCellInt(file, "Sheet3", "A23", 10) - file = SetCellStr(file, "SHEET3", "b230", "10") - file = SetActiveSheet(file, 2) - if err != nil { - t.Log(err) - } + file := OpenFile("./test/Workbook1.xlsx") + file.SetCellInt("SHEET2", "B2", 100) + file.SetCellStr("SHEET2", "C11", "Knowns") + file.NewSheet(3, "TestSheet") + file.SetCellInt("Sheet3", "A23", 10) + file.SetCellStr("SHEET3", "b230", "10") + file.SetCellStr("SHEET10", "b230", "10") + file.SetActiveSheet(2) for i := 1; i <= 300; i++ { - file = SetCellStr(file, "SHEET3", "c"+strconv.Itoa(i), strconv.Itoa(i)) + file.SetCellStr("SHEET3", "c"+strconv.Itoa(i), strconv.Itoa(i)) } - err = Save(file, "./test/Workbook_2.xlsx") + err := file.Save() if err != nil { t.Log(err) } - - // Test save to not exist directory - err = Save(file, "") + // Test write file to given path + err = file.WriteTo("./test/Workbook_2.xlsx") + if err != nil { + t.Log(err) + } + // Test write file to not exist directory + err = file.WriteTo("") if err != nil { t.Log(err) } // Test create a XLSX file file2 := CreateFile() - file2 = NewSheet(file2, 2, "TestSheet2") - file2 = NewSheet(file2, 3, "TestSheet3") - file2 = SetCellInt(file2, "Sheet2", "A23", 10) - file2 = SetCellStr(file2, "SHEET1", "B20", "10") - file2 = SetActiveSheet(file2, 0) - err = Save(file2, "./test/Workbook_3.xlsx") + file2.NewSheet(2, "XLSXSheet2") + file2.NewSheet(3, "XLSXSheet3") + file2.SetCellInt("Sheet2", "A23", 56) + file2.SetCellStr("SHEET1", "B20", "42") + file2.SetActiveSheet(0) + err = file2.WriteTo("./test/Workbook_3.xlsx") if err != nil { t.Log(err) } - // Test read cell value - file, err = OpenFile("./test/Workbook1.xlsx") - if err != nil { - t.Log(err) - } - // Test given illegal rows number - GetCellValue(file, "Sheet2", "a-1") - // Test given lowercase column number - GetCellValue(file, "Sheet2", "a5") - GetCellValue(file, "Sheet2", "C11") - GetCellValue(file, "Sheet2", "D11") - GetCellValue(file, "Sheet2", "D12") - // Test given axis large than exists row - GetCellValue(file, "Sheet2", "E13") + // Test read cell value with given illegal rows number + file.GetCellValue("Sheet2", "a-1") + // Test read cell value with given lowercase column number + file.GetCellValue("Sheet2", "a5") + file.GetCellValue("Sheet2", "C11") + file.GetCellValue("Sheet2", "D11") + file.GetCellValue("Sheet2", "D12") + // Test read cell value with given axis large than exists row + file.GetCellValue("Sheet2", "E13") } diff --git a/file.go b/file.go index a25d66d..e3af5ca 100644 --- a/file.go +++ b/file.go @@ -9,25 +9,27 @@ import ( // CreateFile provide function to create new file by default template // For example: // xlsx := CreateFile() -func CreateFile() map[string]string { +func CreateFile() *File { file := make(map[string]string) - file = saveFileList(file, `_rels/.rels`, templateRels) - file = saveFileList(file, `docProps/app.xml`, templateDocpropsApp) - file = saveFileList(file, `docProps/core.xml`, templateDocpropsCore) - file = saveFileList(file, `xl/_rels/workbook.xml.rels`, templateWorkbookRels) - file = saveFileList(file, `xl/theme/theme1.xml`, templateTheme) - file = saveFileList(file, `xl/worksheets/sheet1.xml`, templateSheet) - file = saveFileList(file, `xl/styles.xml`, templateStyles) - file = saveFileList(file, `xl/workbook.xml`, templateWorkbook) - file = saveFileList(file, `[Content_Types].xml`, templateContentTypes) - return file + file[`_rels/.rels`] = templateRels + file[`docProps/app.xml`] = templateDocpropsApp + file[`docProps/core.xml`] = templateDocpropsCore + file[`xl/_rels/workbook.xml.rels`] = templateWorkbookRels + file[`xl/theme/theme1.xml`] = templateTheme + file[`xl/worksheets/sheet1.xml`] = templateSheet + file[`xl/styles.xml`] = templateStyles + file[`xl/workbook.xml`] = templateWorkbook + file[`[Content_Types].xml`] = templateContentTypes + return &File{ + XLSX: file, + } } -// Save after create or update to an xlsx file at the provided path. -func Save(files map[string]string, name string) error { +// Save provide function override the xlsx file with origin path. +func (f *File) Save() error { buf := new(bytes.Buffer) w := zip.NewWriter(buf) - for path, content := range files { + for path, content := range f.XLSX { f, err := w.Create(path) if err != nil { return err @@ -41,10 +43,36 @@ func Save(files map[string]string, name string) error { if err != nil { return err } - f, err := os.OpenFile(name, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0666) + file, err := os.OpenFile(f.Path, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0666) if err != nil { return err } - buf.WriteTo(f) + buf.WriteTo(file) + return err +} + +// WriteTo provide function create or update to an xlsx file at the provided path. +func (f *File) WriteTo(name string) error { + buf := new(bytes.Buffer) + w := zip.NewWriter(buf) + for path, content := range f.XLSX { + f, err := w.Create(path) + if err != nil { + return err + } + _, err = f.Write([]byte(content)) + if err != nil { + return err + } + } + err := w.Close() + if err != nil { + return err + } + file, err := os.OpenFile(name, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0666) + if err != nil { + return err + } + buf.WriteTo(file) return err } diff --git a/lib.go b/lib.go index 7147424..915bca1 100644 --- a/lib.go +++ b/lib.go @@ -30,17 +30,16 @@ func ReadZipReader(r *zip.Reader) (map[string]string, error) { } // Read XML content as string and replace drawing property in XML namespace of sheet -func readXML(files map[string]string, name string) string { - if content, ok := files[name]; ok { +func (f *File) readXML(name string) string { + if content, ok := f.XLSX[name]; ok { return strings.Replace(content, " 0 { content.BookViews.WorkBookView[0].ActiveTab = index } else { @@ -153,7 +152,7 @@ func SetActiveSheet(file map[string]string, index int) map[string]string { if err != nil { fmt.Println(err) } - file = saveFileList(file, `xl/workbook.xml`, workBookCompatibility(replaceRelationshipsNameSpace(string(output)))) + f.saveFileList(`xl/workbook.xml`, workBookCompatibility(replaceRelationshipsNameSpace(string(output)))) index++ buffer := bytes.Buffer{} for i := 0; i < sheets; i++ { @@ -162,7 +161,7 @@ func SetActiveSheet(file map[string]string, index int) map[string]string { buffer.WriteString(`xl/worksheets/sheet`) buffer.WriteString(strconv.Itoa(sheetIndex)) buffer.WriteString(`.xml`) - xml.Unmarshal([]byte(readXML(file, buffer.String())), &xlsx) + xml.Unmarshal([]byte(f.readXML(buffer.String())), &xlsx) if index == sheetIndex { if len(xlsx.SheetViews.SheetView) > 0 { xlsx.SheetViews.SheetView[0].TabSelected = true @@ -180,10 +179,10 @@ func SetActiveSheet(file map[string]string, index int) map[string]string { if err != nil { fmt.Println(err) } - file = saveFileList(file, buffer.String(), replaceRelationshipsID(replaceWorkSheetsRelationshipsNameSpace(string(sheet)))) + f.saveFileList(buffer.String(), replaceRelationshipsID(replaceWorkSheetsRelationshipsNameSpace(string(sheet)))) buffer.Reset() } - return file + return } // Replace xl/workbook.xml XML tags to self-closing for compatible Office Excel 2007