diff --git a/cell.go b/cell.go index ad99d649b2..67ee4d31d6 100644 --- a/cell.go +++ b/cell.go @@ -1745,3 +1745,18 @@ func shiftCell(val string, dCol, dRow int) string { } return strings.Join(parts, ":") } + +// SetCellEmbedFormula provides a function to set EMBED formula on the cell. +// This is a convenience function for setting object embedding formulas in Excel cells. +// For example: +// +// err := f.SetCellEmbedFormula("Sheet1", "A1", "Package") +// +// This will set the formula =EMBED("Package","") in cell A1. +func (f *File) SetCellEmbedFormula(sheet, cell, objectType string) error { + if objectType == "" { + objectType = "Package" + } + formula := fmt.Sprintf("EMBED(\"%s\",\"\")", objectType) + return f.SetCellFormula(sheet, cell, formula) +} diff --git a/picture.go b/picture.go index 5f7a7d3c53..c2dc5477fa 100644 --- a/picture.go +++ b/picture.go @@ -14,6 +14,7 @@ package excelize import ( "bytes" "encoding/xml" + "fmt" "image" "io" "os" @@ -1018,3 +1019,155 @@ func (f *File) getDispImages(sheet, cell string) ([]Picture, error) { } return pics, err } + +// EmbeddedObjectOptions defines the format set of embedded object. +type EmbeddedObjectOptions struct { + AltText string + PrintObject *bool + Locked *bool + ObjectType string // Default is "Package" +} + +// AddEmbeddedObject provides a method to embed a file as an object in a cell. +// The embedded object will be accessible through Excel's EMBED formula. +// Supported object types include "Package" for general files. For example: +// +// package main +// +// import ( +// "fmt" +// "os" +// +// "github.com/xuri/excelize/v2" +// ) +// +// func main() { +// f := excelize.NewFile() +// defer func() { +// if err := f.Close(); err != nil { +// fmt.Println(err) +// } +// }() +// +// // Read file to embed +// file, err := os.ReadFile("document.pdf") +// if err != nil { +// fmt.Println(err) +// return +// } +// +// // Add embedded object +// if err := f.AddEmbeddedObject("Sheet1", "A1", "document.pdf", file, +// &excelize.EmbeddedObjectOptions{ +// ObjectType: "Package", +// AltText: "Embedded PDF Document", +// }); err != nil { +// fmt.Println(err) +// return +// } +// +// if err := f.SaveAs("Book1.xlsx"); err != nil { +// fmt.Println(err) +// } +// } +func (f *File) AddEmbeddedObject(sheet, cell, filename string, file []byte, opts *EmbeddedObjectOptions) error { + if opts == nil { + opts = &EmbeddedObjectOptions{ + ObjectType: "Package", + PrintObject: boolPtr(true), + Locked: boolPtr(true), + } + } + if opts.ObjectType == "" { + opts.ObjectType = "Package" + } + if opts.PrintObject == nil { + opts.PrintObject = boolPtr(true) + } + if opts.Locked == nil { + opts.Locked = boolPtr(true) + } + + // Add embedded object to package + objPath := f.addEmbeddedObject(file, filename) + + // Add relationships + sheetXMLPath, _ := f.getSheetXMLPath(sheet) + sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetXMLPath, "xl/worksheets/") + ".rels" + rID := f.addRels(sheetRels, SourceRelationshipOLEObject, "../"+objPath, "") + + // Set EMBED formula in cell + formula := fmt.Sprintf("EMBED(\"%s\",\"\")", opts.ObjectType) + if err := f.SetCellFormula(sheet, cell, formula); err != nil { + return err + } + + // Add OLE object to worksheet + ws, err := f.workSheetReader(sheet) + if err != nil { + return err + } + + if ws.OleObjects == nil { + ws.OleObjects = &xlsxInnerXML{} + } + + // Create OLE object XML content + oleObjectXML := fmt.Sprintf(``, rID) + if ws.OleObjects.Content == "" { + ws.OleObjects.Content = oleObjectXML + } else { + ws.OleObjects.Content += oleObjectXML + } + + // Add content type for embedded object + return f.addContentTypePartEmbeddedObject() +} + +// addEmbeddedObject adds embedded object file to the package and returns the path. +func (f *File) addEmbeddedObject(file []byte, filename string) string { + count := f.countEmbeddedObjects() + objPath := "embeddings/oleObject" + strconv.Itoa(count+1) + ".bin" + f.Pkg.Store("xl/"+objPath, file) + return objPath +} + +// countEmbeddedObjects counts the number of embedded objects in the package. +func (f *File) countEmbeddedObjects() int { + count := 0 + f.Pkg.Range(func(k, v interface{}) bool { + if strings.Contains(k.(string), "xl/embeddings/oleObject") { + count++ + } + return true + }) + return count +} + +// addContentTypePartEmbeddedObject adds content type for embedded objects. +func (f *File) addContentTypePartEmbeddedObject() error { + content, err := f.contentTypesReader() + if err != nil { + return err + } + content.mu.Lock() + defer content.mu.Unlock() + + // Check if bin extension already exists + var binExists bool + for _, v := range content.Defaults { + if v.Extension == "bin" { + binExists = true + break + } + } + + if !binExists { + content.Defaults = append(content.Defaults, xlsxDefault{ + Extension: "bin", + ContentType: ContentTypeOLEObject, + }) + } + + return nil +} diff --git a/picture_test.go b/picture_test.go index 28042a99be..b3df08e019 100644 --- a/picture_test.go +++ b/picture_test.go @@ -615,3 +615,70 @@ func TestGetImageCells(t *testing.T) { assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") assert.NoError(t, f.Close()) } + +func TestAddEmbeddedObject(t *testing.T) { + f := NewFile() + defer func() { + assert.NoError(t, f.Close()) + }() + + // Test data for embedding + testData := []byte("This is a test document content") + + // Test adding embedded object with default options + err := f.AddEmbeddedObject("Sheet1", "A1", "test.txt", testData, nil) + assert.NoError(t, err) + + // Verify the EMBED formula was set + formula, err := f.GetCellFormula("Sheet1", "A1") + assert.NoError(t, err) + assert.Equal(t, `EMBED("Package","")`, formula) + + // Test adding embedded object with custom options + err = f.AddEmbeddedObject("Sheet1", "B1", "document.pdf", testData, + &EmbeddedObjectOptions{ + ObjectType: "Package", + AltText: "Embedded PDF Document", + }) + assert.NoError(t, err) + + // Verify the second EMBED formula + formula, err = f.GetCellFormula("Sheet1", "B1") + assert.NoError(t, err) + assert.Equal(t, `EMBED("Package","")`, formula) + + // Test with invalid sheet name + err = f.AddEmbeddedObject("", "A1", "test.txt", testData, nil) + assert.EqualError(t, err, ErrSheetNameBlank.Error()) + + // Test with invalid cell reference + err = f.AddEmbeddedObject("Sheet1", "", "test.txt", testData, nil) + assert.EqualError(t, err, `cannot convert cell "" to coordinates: invalid cell name ""`) +} + +func TestSetCellEmbedFormula(t *testing.T) { + f := NewFile() + defer func() { + assert.NoError(t, f.Close()) + }() + + // Test setting EMBED formula with default Package type + err := f.SetCellEmbedFormula("Sheet1", "A1", "") + assert.NoError(t, err) + + formula, err := f.GetCellFormula("Sheet1", "A1") + assert.NoError(t, err) + assert.Equal(t, `EMBED("Package","")`, formula) + + // Test setting EMBED formula with custom object type + err = f.SetCellEmbedFormula("Sheet1", "B1", "Document") + assert.NoError(t, err) + + formula, err = f.GetCellFormula("Sheet1", "B1") + assert.NoError(t, err) + assert.Equal(t, `EMBED("Document","")`, formula) + + // Test with invalid sheet name + err = f.SetCellEmbedFormula("", "A1", "Package") + assert.EqualError(t, err, ErrSheetNameBlank.Error()) +} diff --git a/templates.go b/templates.go index bd027b8a30..4e5de47a7a 100644 --- a/templates.go +++ b/templates.go @@ -62,6 +62,7 @@ const ( ContentTypeTemplateMacro = "application/vnd.ms-excel.template.macroEnabled.main+xml" ContentTypeVBA = "application/vnd.ms-office.vbaProject" ContentTypeVML = "application/vnd.openxmlformats-officedocument.vmlDrawing" + ContentTypeOLEObject = "application/vnd.openxmlformats-officedocument.oleObject" NameSpaceDrawingMLMain = "http://schemas.openxmlformats.org/drawingml/2006/main" NameSpaceDublinCore = "http://purl.org/dc/elements/1.1/" NameSpaceDublinCoreMetadataInitiative = "http://purl.org/dc/dcmitype/" @@ -88,6 +89,8 @@ const ( SourceRelationshipTable = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/table" SourceRelationshipVBAProject = "http://schemas.microsoft.com/office/2006/relationships/vbaProject" SourceRelationshipWorkSheet = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" + SourceRelationshipOLEObject = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/oleObject" + SourceRelationshipPackage = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/package" StrictNameSpaceDocumentPropertiesVariantTypes = "http://purl.oclc.org/ooxml/officeDocument/docPropsVTypes" StrictNameSpaceDrawingMLMain = "http://purl.oclc.org/ooxml/drawingml/main" StrictNameSpaceExtendedProperties = "http://purl.oclc.org/ooxml/officeDocument/extendedProperties"