Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit a9d3ee2
Showing
12 changed files
with
1,237 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
* Excelize | ||
|
||
![Excelize](./excelize.png "Excelize") | ||
|
||
** Introduction | ||
Excelize is a library written in pure Golang and providing a set of function that allow you to write to and read from XLSX files. | ||
|
||
|
||
|
||
** Basic Usage | ||
|
||
*** Installation | ||
|
||
``` | ||
go get github.com/luxurioust/excelize | ||
``` | ||
|
||
*** Create XLSX files | ||
|
||
Here is a minimal example usage that will create XLSX file. | ||
|
||
``` | ||
package main | ||
import ( | ||
"fmt" | ||
"github.com/luxurioust/excelize" | ||
) | ||
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, "~/Workbook.xlsx") | ||
if err != nil { | ||
fmt.Println(err) | ||
} | ||
} | ||
``` | ||
|
||
*** Writing XLSX files | ||
|
||
The following constitutes the bare minimum required to write an XLSX document. | ||
|
||
``` | ||
package main | ||
import ( | ||
"fmt" | ||
"github.com/luxurioust/excelize" | ||
) | ||
func main() { | ||
xlsx, err := excelize.Openxlsx("~/Workbook.xlsx") | ||
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) | ||
if err != nil { | ||
fmt.Println(err) | ||
} | ||
err = excelize.Save(xlsx, "~/Workbook.xlsx") | ||
} | ||
``` | ||
|
||
** Contributing | ||
|
||
Contributions are welcome! Open a pull request to fix a bug, or open an issue to discuss a new feature or change. | ||
|
||
** Licenses | ||
|
||
This program is under the terms of the BSD 3-Clause License. See <https://opensource.org/licenses/BSD-3-Clause>. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
package excelize | ||
|
||
import ( | ||
"archive/zip" | ||
"encoding/xml" | ||
"fmt" | ||
"strconv" | ||
"strings" | ||
) | ||
|
||
type FileList struct { | ||
Key string | ||
Value string | ||
} | ||
|
||
// OpenFile() take the name of an XLSX file and returns a populated | ||
// xlsx.File struct for it. | ||
func OpenFile(filename string) (file []FileList, err error) { | ||
var f *zip.ReadCloser | ||
f, err = zip.OpenReader(filename) | ||
if err != nil { | ||
return nil, err | ||
} | ||
file, err = ReadZip(f) | ||
return | ||
} | ||
|
||
// Set int type value of a cell | ||
func SetCellInt(file []FileList, sheet string, axis string, value int) []FileList { | ||
axis = strings.ToUpper(axis) | ||
var xlsx xlsxWorksheet | ||
col := getColIndex(axis) | ||
row := getRowIndex(axis) | ||
xAxis := row - 1 | ||
yAxis := titleToNumber(col) | ||
|
||
name := fmt.Sprintf("xl/worksheets/%s.xml", strings.ToLower(sheet)) | ||
xml.Unmarshal([]byte(readXml(file, name)), &xlsx) | ||
|
||
rows := xAxis + 1 | ||
cell := yAxis + 1 | ||
|
||
xlsx = checkRow(xlsx) | ||
|
||
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) | ||
|
||
output, err := xml.MarshalIndent(xlsx, "", "") | ||
if err != nil { | ||
fmt.Println(err) | ||
} | ||
saveFileList(file, name, replaceRelationshipsID(replaceWorkSheetsRelationshipsNameSpace(string(output)))) | ||
return file | ||
} | ||
|
||
// Set string type value of a cell | ||
func SetCellStr(file []FileList, sheet string, axis string, value string) []FileList { | ||
axis = strings.ToUpper(axis) | ||
var xlsx xlsxWorksheet | ||
col := getColIndex(axis) | ||
row := getRowIndex(axis) | ||
xAxis := row - 1 | ||
yAxis := titleToNumber(col) | ||
|
||
name := fmt.Sprintf("xl/worksheets/%s.xml", strings.ToLower(sheet)) | ||
xml.Unmarshal([]byte(readXml(file, name)), &xlsx) | ||
|
||
rows := xAxis + 1 | ||
cell := yAxis + 1 | ||
|
||
xlsx = checkRow(xlsx) | ||
xlsx = completeRow(xlsx, rows, cell) | ||
xlsx = completeCol(xlsx, rows, cell) | ||
|
||
xlsx.SheetData.Row[xAxis].C[yAxis].T = "str" | ||
xlsx.SheetData.Row[xAxis].C[yAxis].V = value | ||
|
||
output, err := xml.MarshalIndent(xlsx, "", "") | ||
if err != nil { | ||
fmt.Println(err) | ||
} | ||
saveFileList(file, name, replaceRelationshipsID(replaceWorkSheetsRelationshipsNameSpace(string(output)))) | ||
return file | ||
} | ||
|
||
// Completion column element tags of XML in a sheet | ||
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, | ||
}) | ||
} | ||
} | ||
for k, v := range xlsx.SheetData.Row { | ||
if len(v.C) < cell { | ||
start := len(v.C) | ||
for iii := start; iii < cell; iii++ { | ||
xlsx.SheetData.Row[k].C = append(xlsx.SheetData.Row[k].C, xlsxC{ | ||
R: toAlphaString(iii+1) + strconv.Itoa(k+1), | ||
}) | ||
} | ||
} | ||
} | ||
return xlsx | ||
} | ||
|
||
// Completion row element tags of XML in a sheet | ||
func completeRow(xlsx xlsxWorksheet, row int, cell int) xlsxWorksheet { | ||
if len(xlsx.SheetData.Row) < row { | ||
for i := len(xlsx.SheetData.Row); i < row; i++ { | ||
xlsx.SheetData.Row = append(xlsx.SheetData.Row, xlsxRow{ | ||
R: i + 1, | ||
}) | ||
} | ||
|
||
for ii := 0; ii < row; ii++ { | ||
start := len(xlsx.SheetData.Row[ii].C) | ||
if start == 0 { | ||
for iii := start; iii < cell; iii++ { | ||
xlsx.SheetData.Row[ii].C = append(xlsx.SheetData.Row[ii].C, xlsxC{ | ||
R: toAlphaString(iii+1) + strconv.Itoa(ii+1), | ||
}) | ||
} | ||
} | ||
} | ||
} | ||
return xlsx | ||
} | ||
|
||
// Replace xl/worksheets/sheet%d.xml XML tags to self-closing for compatible Office Excel 2007 | ||
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) | ||
workbookMarshal = strings.Replace(workbookMarshal, `></sheetPr>`, ` />`, -1) | ||
workbookMarshal = strings.Replace(workbookMarshal, `></dimension>`, ` />`, -1) | ||
workbookMarshal = strings.Replace(workbookMarshal, `></selection>`, ` />`, -1) | ||
workbookMarshal = strings.Replace(workbookMarshal, `></sheetFormatPr>`, ` />`, -1) | ||
workbookMarshal = strings.Replace(workbookMarshal, `></printOptions>`, ` />`, -1) | ||
workbookMarshal = strings.Replace(workbookMarshal, `></pageSetup>`, ` />`, -1) | ||
workbookMarshal = strings.Replace(workbookMarshal, `></pageMargins>`, ` />`, -1) | ||
workbookMarshal = strings.Replace(workbookMarshal, `></headerFooter>`, ` />`, -1) | ||
workbookMarshal = strings.Replace(workbookMarshal, `></drawing>`, ` />`, -1) | ||
return workbookMarshal | ||
} | ||
|
||
// Check XML tags and fix discontinuous case, for example: | ||
// | ||
// <row r="15" spans="1:22" x14ac:dyDescent="0.2"> | ||
// <c r="A15" s="2" /> | ||
// <c r="B15" s="2" /> | ||
// <c r="F15" s="1" /> | ||
// <c r="G15" s="1" /> | ||
// </row> | ||
// | ||
// in this case, we should to change it to | ||
// | ||
// <row r="15" spans="1:22" x14ac:dyDescent="0.2"> | ||
// <c r="A15" s="2" /> | ||
// <c r="B15" s="2" /> | ||
// <c r="C15" s="2" /> | ||
// <c r="D15" s="2" /> | ||
// <c r="E15" s="2" /> | ||
// <c r="F15" s="1" /> | ||
// <c r="G15" s="1" /> | ||
// </row> | ||
// | ||
func checkRow(xlsx xlsxWorksheet) xlsxWorksheet { | ||
for k, v := range xlsx.SheetData.Row { | ||
lenCol := len(v.C) | ||
endR := getColIndex(v.C[lenCol-1].R) | ||
endRow := getRowIndex(v.C[lenCol-1].R) | ||
endCol := titleToNumber(endR) | ||
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++ { | ||
fixAxis := toAlphaString(i+1) + strconv.Itoa(endRow) | ||
tmp = append(tmp, xlsxC{ | ||
R: fixAxis, | ||
}) | ||
} | ||
xlsx.SheetData.Row[k].C = tmp | ||
for _, y := range oldRow { | ||
colAxis := titleToNumber(getColIndex(y.R)) | ||
xlsx.SheetData.Row[k].C[colAxis] = y | ||
} | ||
} | ||
} | ||
return xlsx | ||
} |
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package excelize | ||
|
||
import ( | ||
"fmt" | ||
"math/rand" | ||
"sync" | ||
"testing" | ||
"time" | ||
) | ||
|
||
var ( | ||
once sync.Once | ||
) | ||
|
||
func testSetup() { | ||
rand.Seed(time.Now().UnixNano()) | ||
} | ||
|
||
func TestExcelize(t *testing.T) { | ||
// Test update a XLSX file | ||
file, err := OpenFile("./test/Workbook1.xlsx") | ||
if err != nil { | ||
fmt.Println(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 { | ||
fmt.Println(err) | ||
} | ||
for i := 1; i <= 300; i++ { | ||
file = SetCellStr(file, "SHEET3", fmt.Sprintf("c%d", i), randToken(5)) | ||
} | ||
err = Save(file, "./test/Workbook_2.xlsx") | ||
|
||
// Test create a XLSX file | ||
file2 := CreateFile() | ||
file2 = NewSheet(file2, 2, "SHEETxxx") | ||
file2 = NewSheet(file2, 3, "asd") | ||
file2 = SetCellInt(file2, "Sheet2", "A23", 10) | ||
file2 = SetCellStr(file2, "SHEET1", "B20", "10") | ||
err = Save(file2, "./test/Workbook_3.xlsx") | ||
if err != nil { | ||
fmt.Println(err) | ||
} | ||
} | ||
|
||
func randToken(length int) string { | ||
b := make([]byte, length) | ||
rand.Read(b) | ||
return fmt.Sprintf("%x", b) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package excelize | ||
|
||
import ( | ||
"archive/zip" | ||
"bytes" | ||
"fmt" | ||
"os" | ||
) | ||
|
||
// Create a new xlsx file | ||
// | ||
// For example: | ||
// | ||
// xlsx := CreateFile() | ||
// | ||
func CreateFile() []FileList { | ||
var file []FileList | ||
file = saveFileList(file, `_rels/.rels`, TEMPLATE_RELS) | ||
file = saveFileList(file, `docProps/app.xml`, TEMPLATE_DOCPROPS_APP) | ||
file = saveFileList(file, `docProps/core.xml`, TEMPLATE_DOCPROPS_CORE) | ||
file = saveFileList(file, `xl/_rels/workbook.xml.rels`, TEMPLATE_WORKBOOK_RELS) | ||
file = saveFileList(file, `xl/theme/theme1.xml`, TEMPLATE_THEME) | ||
file = saveFileList(file, `xl/worksheets/sheet1.xml`, TEMPLATE_SHEET) | ||
file = saveFileList(file, `xl/styles.xml`, TEMPLATE_STYLES) | ||
file = saveFileList(file, `xl/workbook.xml`, TEMPLATE_WORKBOOK) | ||
file = saveFileList(file, `[Content_Types].xml`, TEMPLATE_CONTENT_TYPES) | ||
return file | ||
} | ||
|
||
// Save after create or update to an xlsx file at the provided path. | ||
func Save(files []FileList, name string) error { | ||
buf := new(bytes.Buffer) | ||
w := zip.NewWriter(buf) | ||
for _, file := range files { | ||
f, err := w.Create(file.Key) | ||
if err != nil { | ||
fmt.Println(err) | ||
} | ||
_, err = f.Write([]byte(file.Value)) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
err := w.Close() | ||
if err != nil { | ||
return err | ||
} | ||
f, err := os.OpenFile(name, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0666) | ||
if err != nil { | ||
return err | ||
} | ||
buf.WriteTo(f) | ||
return err | ||
} |
Oops, something went wrong.