Jelajahi Sumber

- Add hyperlink and set formula support for cell support;
- Character limits for cells added;
- Update go test and fix typo

Ri Xu 9 tahun lalu
induk
melakukan
4a9b39afc6
8 mengubah file dengan 196 tambahan dan 91 penghapusan
  1. 6 1
      README.md
  2. 59 4
      cell.go
  3. 5 1
      excelize.go
  4. 110 71
      excelize_test.go
  5. 6 5
      picture.go
  6. 4 5
      sheet.go
  7. 1 0
      xmlDrawing.go
  8. 5 4
      xmlWorkbook.go

+ 6 - 1
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)
+    }
 }
 ```
 

+ 59 - 4
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)))
+}

+ 5 - 1
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))

+ 110 - 71
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)
 	}

+ 6 - 5
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 {

+ 4 - 5
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

+ 1 - 0
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"

+ 5 - 4
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