From 4a9b39afc634a2399c4729f4fb47c9f290ab1ee5 Mon Sep 17 00:00:00 2001 From: Ri Xu Date: Thu, 19 Jan 2017 14:05:32 +0800 Subject: [PATCH] - Add hyperlink and set formula support for cell support; - Character limits for cells added; - Update go test and fix typo --- README.md | 7 +- cell.go | 63 +++++++++++++++-- excelize.go | 6 +- excelize_test.go | 181 ++++++++++++++++++++++++++++------------------- picture.go | 11 +-- sheet.go | 9 ++- xmlDrawing.go | 1 + xmlWorkbook.go | 9 +-- 8 files changed, 196 insertions(+), 91 deletions(-) diff --git a/README.md b/README.md index 1c77058..f9ead95 100644 --- a/README.md +++ b/README.md @@ -106,7 +106,7 @@ func main() { } ``` -### Add pictures to XLSX files +### Add picture to XLSX files ```go package main @@ -125,6 +125,11 @@ func main() { fmt.Println(err) os.Exit(1) } + err = xlsx.WriteTo("/tmp/Workbook.xlsx") + if err != nil { + fmt.Println(err) + os.Exit(1) + } } ``` diff --git a/cell.go b/cell.go index f91b661..5f8439e 100644 --- a/cell.go +++ b/cell.go @@ -6,8 +6,9 @@ import ( "strings" ) -// GetCellValue provide function get value from cell by given sheet index and -// axis in XLSX file. The value of the merged cell is not available currently. +// GetCellValue provides function to get value from cell by given sheet index +// and axis in XLSX file. The value of the merged cell is not available +// currently. func (f *File) GetCellValue(sheet string, axis string) string { axis = strings.ToUpper(axis) var xlsx xlsxWorksheet @@ -50,8 +51,8 @@ func (f *File) GetCellValue(sheet string, axis string) string { return "" } -// GetCellFormula provide function get formula from cell by given sheet index -// and axis in XLSX file. +// GetCellFormula provides function to get formula from cell by given sheet +// index and axis in XLSX file. func (f *File) GetCellFormula(sheet string, axis string) string { axis = strings.ToUpper(axis) var xlsx xlsxWorksheet @@ -84,3 +85,57 @@ func (f *File) GetCellFormula(sheet string, axis string) string { } return "" } + +// SetCellHyperLink provides function to set cell hyperlink by given sheet index +// and link URL address. Only support external link currently. +func (f *File) SetCellHyperLink(sheet, axis, link string) { + axis = strings.ToUpper(axis) + var xlsx xlsxWorksheet + name := "xl/worksheets/" + strings.ToLower(sheet) + ".xml" + xml.Unmarshal([]byte(f.readXML(name)), &xlsx) + rID := f.addSheetRelationships(sheet, SourceRelationshipHyperLink, link, "External") + hyperlink := xlsxHyperlink{ + Ref: axis, + RID: "rId" + strconv.Itoa(rID), + } + if xlsx.Hyperlinks != nil { + xlsx.Hyperlinks.Hyperlink = append(xlsx.Hyperlinks.Hyperlink, hyperlink) + } else { + hyperlinks := xlsxHyperlinks{} + hyperlinks.Hyperlink = append(hyperlinks.Hyperlink, hyperlink) + xlsx.Hyperlinks = &hyperlinks + } + output, _ := xml.Marshal(xlsx) + f.saveFileList(name, replaceWorkSheetsRelationshipsNameSpace(string(output))) +} + +// SetCellFormula provides function to set cell formula by given string and +// sheet index. +func (f *File) SetCellFormula(sheet, axis, formula string) { + 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) + + name := "xl/worksheets/" + strings.ToLower(sheet) + ".xml" + xml.Unmarshal([]byte(f.readXML(name)), &xlsx) + + rows := xAxis + 1 + cell := yAxis + 1 + + xlsx = completeRow(xlsx, rows, cell) + xlsx = completeCol(xlsx, rows, cell) + + 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 + } + output, _ := xml.Marshal(xlsx) + f.saveFileList(name, replaceWorkSheetsRelationshipsNameSpace(string(output))) +} diff --git a/excelize.go b/excelize.go index fed9613..d43f123 100644 --- a/excelize.go +++ b/excelize.go @@ -85,9 +85,13 @@ func (f *File) SetCellInt(sheet string, axis string, value int) { f.saveFileList(name, replaceWorkSheetsRelationshipsNameSpace(string(output))) } -// SetCellStr provides function to set string type value of a cell. +// SetCellStr provides function to set string type value of a cell. Total number +// of characters that a cell can contain 32767 characters. func (f *File) SetCellStr(sheet string, axis string, value string) { axis = strings.ToUpper(axis) + if len(value) > 32767 { + value = value[0:32767] + } var xlsx xlsxWorksheet col := string(strings.Map(letterOnlyMapF, axis)) row, _ := strconv.Atoi(strings.Map(intOnlyMapF, axis)) diff --git a/excelize_test.go b/excelize_test.go index 6e41968..278912b 100644 --- a/excelize_test.go +++ b/excelize_test.go @@ -7,108 +7,119 @@ import ( func TestOpenFile(t *testing.T) { // Test update a XLSX file. - f1, err := OpenFile("./test/Workbook1.xlsx") + xlsx, err := OpenFile("./test/Workbook1.xlsx") if err != nil { t.Log(err) } // Test get all the rows in a not exists sheet. - rows := f1.GetRows("Sheet4") + rows := xlsx.GetRows("Sheet4") // Test get all the rows in a sheet. - rows = f1.GetRows("Sheet2") + rows = xlsx.GetRows("Sheet2") for _, row := range rows { for _, cell := range row { t.Log(cell, "\t") } t.Log("\r\n") } - f1.UpdateLinkedValue() - f1.SetCellDefault("SHEET2", "A1", strconv.FormatFloat(float64(100.1588), 'f', -1, 32)) - f1.SetCellDefault("SHEET2", "A1", strconv.FormatFloat(float64(-100.1588), 'f', -1, 64)) - f1.SetCellInt("SHEET2", "A1", 100) - f1.SetCellStr("SHEET2", "C11", "Knowns") - f1.NewSheet(3, ":\\/?*[]Maximum 31 characters allowed in sheet title.") + 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)) + xlsx.SetCellInt("SHEET2", "A1", 100) + xlsx.SetCellStr("SHEET2", "C11", "Knowns") + // Test max characters in a cell. + var s = "c" + for i := 0; i < 32768; i++ { + s += "c" + } + xlsx.SetCellStr("SHEET2", "D11", s) + xlsx.NewSheet(3, ":\\/?*[]Maximum 31 characters allowed in sheet title.") // Test set sheet name with illegal name. - f1.SetSheetName("Maximum 31 characters allowed i", "[Rename]:\\/?* Maximum 31 characters allowed in sheet title.") - f1.SetCellInt("Sheet3", "A23", 10) - f1.SetCellStr("SHEET3", "b230", "10") - f1.SetCellStr("SHEET10", "b230", "10") - f1.SetActiveSheet(2) - f1.GetCellFormula("Sheet1", "B19") // Test get cell formula with given rows number. - f1.GetCellFormula("Sheet2", "B20") // Test get cell formula with illegal sheet index. - f1.GetCellFormula("Sheet1", "B20") // Test get cell formula with illegal rows number. + xlsx.SetSheetName("Maximum 31 characters allowed i", "[Rename]:\\/?* Maximum 31 characters allowed in sheet title.") + xlsx.SetCellInt("Sheet3", "A23", 10) + xlsx.SetCellStr("SHEET3", "b230", "10") + xlsx.SetCellStr("SHEET10", "b230", "10") + xlsx.SetActiveSheet(2) + xlsx.GetCellFormula("Sheet1", "B19") // Test get cell formula with given rows number. + xlsx.GetCellFormula("Sheet2", "B20") // Test get cell formula with illegal sheet index. + xlsx.GetCellFormula("Sheet1", "B20") // Test get cell formula with illegal rows number. // Test read cell value with given illegal rows number. - f1.GetCellValue("Sheet2", "a-1") + xlsx.GetCellValue("Sheet2", "a-1") // Test read cell value with given lowercase column number. - f1.GetCellValue("Sheet2", "a5") - f1.GetCellValue("Sheet2", "C11") - f1.GetCellValue("Sheet2", "D11") - f1.GetCellValue("Sheet2", "D12") + xlsx.GetCellValue("Sheet2", "a5") + xlsx.GetCellValue("Sheet2", "C11") + xlsx.GetCellValue("Sheet2", "D11") + xlsx.GetCellValue("Sheet2", "D12") // Test SetCellValue function. - f1.SetCellValue("Sheet2", "F1", "Hello") - f1.SetCellValue("Sheet2", "G1", []byte("World")) - f1.SetCellValue("Sheet2", "F2", 42) - f1.SetCellValue("Sheet2", "F2", int8(42)) - f1.SetCellValue("Sheet2", "F2", int16(42)) - f1.SetCellValue("Sheet2", "F2", int32(42)) - f1.SetCellValue("Sheet2", "F2", int64(42)) - f1.SetCellValue("Sheet2", "F2", float32(42.65418)) - f1.SetCellValue("Sheet2", "F2", float64(-42.65418)) - f1.SetCellValue("Sheet2", "F2", float32(42)) - f1.SetCellValue("Sheet2", "F2", float64(42)) - f1.SetCellValue("Sheet2", "G2", nil) + xlsx.SetCellValue("Sheet2", "F1", "Hello") + xlsx.SetCellValue("Sheet2", "G1", []byte("World")) + xlsx.SetCellValue("Sheet2", "F2", 42) + xlsx.SetCellValue("Sheet2", "F2", int8(42)) + xlsx.SetCellValue("Sheet2", "F2", int16(42)) + xlsx.SetCellValue("Sheet2", "F2", int32(42)) + xlsx.SetCellValue("Sheet2", "F2", int64(42)) + xlsx.SetCellValue("Sheet2", "F2", float32(42.65418)) + xlsx.SetCellValue("Sheet2", "F2", float64(-42.65418)) + xlsx.SetCellValue("Sheet2", "F2", float32(42)) + xlsx.SetCellValue("Sheet2", "F2", float64(42)) + xlsx.SetCellValue("Sheet2", "G2", nil) // Test completion column. - f1.SetCellValue("Sheet2", "M2", nil) + xlsx.SetCellValue("Sheet2", "M2", nil) // Test read cell value with given axis large than exists row. - f1.GetCellValue("Sheet2", "E231") + xlsx.GetCellValue("Sheet2", "E231") // Test get active sheet of XLSX and get sheet name of XLSX by given sheet index. - f1.GetSheetName(f1.GetActiveSheetIndex()) + xlsx.GetSheetName(xlsx.GetActiveSheetIndex()) // Test get sheet name of XLSX by given invalid sheet index. - f1.GetSheetName(4) + xlsx.GetSheetName(4) // Test get sheet map of XLSX. - f1.GetSheetMap() - + xlsx.GetSheetMap() for i := 1; i <= 300; i++ { - f1.SetCellStr("SHEET3", "c"+strconv.Itoa(i), strconv.Itoa(i)) + xlsx.SetCellStr("SHEET3", "c"+strconv.Itoa(i), strconv.Itoa(i)) + } + err = xlsx.Save() + if err != nil { + t.Log(err) + } + // Test write file to not exist directory. + err = xlsx.WriteTo("") + if err != nil { + t.Log(err) } - err = f1.Save() +} + +func TestAddPicture(t *testing.T) { + xlsx, err := OpenFile("./test/Workbook1.xlsx") if err != nil { t.Log(err) } // Test add picture to sheet. - err = f1.AddPicture("Sheet2", "I1", "L10", "./test/images/excel.jpg") + err = xlsx.AddPicture("Sheet2", "I1", "L10", "./test/images/excel.jpg") if err != nil { t.Log(err) } - err = f1.AddPicture("Sheet1", "F21", "G25", "./test/images/excel.png") + err = xlsx.AddPicture("Sheet1", "F21", "G25", "./test/images/excel.png") if err != nil { t.Log(err) } - err = f1.AddPicture("Sheet2", "L1", "O10", "./test/images/excel.bmp") + err = xlsx.AddPicture("Sheet2", "L1", "O10", "./test/images/excel.bmp") if err != nil { t.Log(err) } - err = f1.AddPicture("Sheet1", "G21", "H25", "./test/images/excel.ico") + err = xlsx.AddPicture("Sheet1", "G21", "H25", "./test/images/excel.ico") if err != nil { t.Log(err) } // Test add picture to sheet with unsupport file type. - err = f1.AddPicture("Sheet1", "G21", "H25", "./test/images/excel.icon") + err = xlsx.AddPicture("Sheet1", "G21", "H25", "./test/images/excel.icon") if err != nil { t.Log(err) } // Test add picture to sheet with invalid file path. - err = f1.AddPicture("Sheet1", "G21", "H25", "./test/Workbook1.xlsx") + err = xlsx.AddPicture("Sheet1", "G21", "H25", "./test/Workbook1.xlsx") if err != nil { t.Log(err) } - // Test write file to given path. - err = f1.WriteTo("./test/Workbook_2.xlsx") - if err != nil { - t.Log(err) - } - // Test write file to not exist directory. - err = f1.WriteTo("") + err = xlsx.WriteTo("./test/Workbook_2.xlsx") if err != nil { t.Log(err) } @@ -116,13 +127,13 @@ func TestOpenFile(t *testing.T) { func TestBrokenFile(t *testing.T) { // Test write file with broken file struct. - f2 := File{} - err := f2.Save() + xlsx := File{} + err := xlsx.Save() if err != nil { t.Log(err) } // Test write file with broken file struct with given path. - err = f2.WriteTo("./test/Workbook_3.xlsx") + err = xlsx.WriteTo("./test/Workbook_3.xlsx") if err != nil { t.Log(err) } @@ -143,35 +154,63 @@ func TestBrokenFile(t *testing.T) { func TestCreateFile(t *testing.T) { // Test create a XLSX file. - f4 := CreateFile() - f4.NewSheet(2, "XLSXSheet2") - f4.NewSheet(3, "XLSXSheet3") - f4.SetCellInt("Sheet2", "A23", 56) - f4.SetCellStr("SHEET1", "B20", "42") - f4.SetActiveSheet(0) + xlsx := CreateFile() + xlsx.NewSheet(2, "XLSXSheet2") + xlsx.NewSheet(3, "XLSXSheet3") + xlsx.SetCellInt("Sheet2", "A23", 56) + xlsx.SetCellStr("SHEET1", "B20", "42") + xlsx.SetActiveSheet(0) // Test add picture to sheet. - err := f4.AddPicture("Sheet1", "H2", "K12", "./test/images/excel.gif") + err := xlsx.AddPicture("Sheet1", "H2", "K12", "./test/images/excel.gif") if err != nil { t.Log(err) } - err = f4.AddPicture("Sheet1", "C2", "F12", "./test/images/excel.tif") + err = xlsx.AddPicture("Sheet1", "C2", "F12", "./test/images/excel.tif") if err != nil { t.Log(err) } - err = f4.WriteTo("./test/Workbook_3.xlsx") + err = xlsx.WriteTo("./test/Workbook_3.xlsx") if err != nil { t.Log(err) } } func TestSetColWidth(t *testing.T) { - f5, err := OpenFile("./test/Workbook1.xlsx") + xlsx, err := OpenFile("./test/Workbook1.xlsx") + if err != nil { + t.Log(err) + } + xlsx.SetColWidth("sheet1", "B", "A", 12) + xlsx.SetColWidth("sheet1", "A", "B", 12) + err = xlsx.Save() + if err != nil { + t.Log(err) + } +} + +func TestSetCellHyperLink(t *testing.T) { + xlsx, err := OpenFile("./test/Workbook1.xlsx") + if err != nil { + t.Log(err) + } + // Test set cell hyperlink in a work sheet already have hyperlinks. + xlsx.SetCellHyperLink("sheet1", "B19", "https://github.com/Luxurioust/excelize") + // Test add first hyperlink in a work sheet. + xlsx.SetCellHyperLink("sheet2", "C1", "https://github.com/Luxurioust/excelize") + err = xlsx.Save() + if err != nil { + t.Log(err) + } +} + +func TestSetCellFormula(t *testing.T) { + xlsx, err := OpenFile("./test/Workbook1.xlsx") if err != nil { t.Log(err) } - f5.SetColWidth("sheet1", "B", "A", 12) - f5.SetColWidth("sheet1", "A", "B", 12) - err = f5.Save() + xlsx.SetCellFormula("sheet1", "B19", "SUM(Sheet2!D2,Sheet2!D11)") + xlsx.SetCellFormula("sheet1", "C19", "SUM(Sheet2!D2,Sheet2!D9)") + err = xlsx.Save() if err != nil { t.Log(err) } diff --git a/picture.go b/picture.go index 62559de..bbd7cf3 100644 --- a/picture.go +++ b/picture.go @@ -53,7 +53,7 @@ func (f *File) AddPicture(sheet string, xAxis string, yAxis string, picture stri drawingXML = strings.Replace(sheetRelationshipsDrawingXML, "..", "xl", -1) } else { // Add first picture for given sheet. - rID := f.addSheetRelationships(sheet, SourceRelationshipDrawingML, sheetRelationshipsDrawingXML) + rID := f.addSheetRelationships(sheet, SourceRelationshipDrawingML, sheetRelationshipsDrawingXML, "") f.addSheetDrawing(sheet, rID) } drawingRID = f.addDrawingRelationships(drawingID, SourceRelationshipImage, "../media/image"+strconv.Itoa(pictureID)+ext) @@ -66,7 +66,7 @@ func (f *File) AddPicture(sheet string, xAxis string, yAxis string, picture stri // addSheetRelationships provides function to add // xl/worksheets/_rels/sheet%d.xml.rels by given sheet name, relationship type // and target. -func (f *File) addSheetRelationships(sheet string, relType string, target string) int { +func (f *File) addSheetRelationships(sheet, relType, target, targetMode string) int { var rels = "xl/worksheets/_rels/" + strings.ToLower(sheet) + ".xml.rels" var sheetRels xlsxWorkbookRels var rID = 1 @@ -82,9 +82,10 @@ func (f *File) addSheetRelationships(sheet string, relType string, target string ID.WriteString(strconv.Itoa(rID)) } sheetRels.Relationships = append(sheetRels.Relationships, xlsxWorkbookRelation{ - ID: ID.String(), - Type: relType, - Target: target, + ID: ID.String(), + Type: relType, + Target: target, + TargetMode: targetMode, }) output, err := xml.Marshal(sheetRels) if err != nil { diff --git a/sheet.go b/sheet.go index 3f117af..a3df037 100644 --- a/sheet.go +++ b/sheet.go @@ -8,10 +8,9 @@ import ( "strings" ) -// Sprint formats using the default formats for its operands and returns the -// resulting string. NewSheet provice function to greate a new sheet by given -// index, when creating a new XLSX file, the default sheet will be create, when -// you create a new file, you need to ensure that the index is continuous. +// NewSheet provice function to greate a new sheet by given index, when creating +// a new XLSX file, the default sheet will be create, when you create a new +// file, you need to ensure that the index is continuous. func (f *File) NewSheet(index int, name string) { // Update docProps/app.xml f.setAppXML() @@ -126,7 +125,7 @@ func replaceRelationshipsNameSpace(workbookMarshal string) string { return strings.Replace(workbookMarshal, oldXmlns, newXmlns, -1) } -// SetActiveSheet provide function to set default active sheet of XLSX by given +// SetActiveSheet provides function to set default active sheet of XLSX by given // index. func (f *File) SetActiveSheet(index int) { var content xlsxWorkbook diff --git a/xmlDrawing.go b/xmlDrawing.go index 89854ec..efcfa81 100644 --- a/xmlDrawing.go +++ b/xmlDrawing.go @@ -5,6 +5,7 @@ const ( SourceRelationship = "http://schemas.openxmlformats.org/officeDocument/2006/relationships" SourceRelationshipImage = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" SourceRelationshipDrawingML = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing" + SourceRelationshipHyperLink = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink" SourceRelationshipWorkSheet = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" NameSpaceDrawingML = "http://schemas.openxmlformats.org/drawingml/2006/main" NameSpaceSpreadSheetDrawing = "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing" diff --git a/xmlWorkbook.go b/xmlWorkbook.go index 775d01b..22e9e48 100644 --- a/xmlWorkbook.go +++ b/xmlWorkbook.go @@ -16,11 +16,12 @@ type xlsxWorkbookRels struct { Relationships []xlsxWorkbookRelation `xml:"Relationship"` } -// xmlxWorkbookRelation maps sheet id and xl/worksheets/sheet%d.xml +// xmlxWorkbookRelation maps sheet id and xl/worksheets/_rels/sheet%d.xml.rels type xlsxWorkbookRelation struct { - ID string `xml:"Id,attr"` - Target string `xml:",attr"` - Type string `xml:",attr"` + ID string `xml:"Id,attr"` + Target string `xml:",attr"` + Type string `xml:",attr"` + TargetMode string `xml:",attr,omitempty"` } // xlsxWorkbook directly maps the workbook element from the namespace