Skip to content

Commit 2b6b878

Browse files
Copilotxuri
andcommitted
Implement support for Excel EMBED function and object file embedding
- Add AddEmbeddedObject function to embed files as objects in cells - Add SetCellEmbedFormula convenience function for EMBED formulas - Add content types and relationship support for OLE objects - Include comprehensive tests for both functions - Support Package and other object types for embedding Co-authored-by: xuri <[email protected]>
1 parent ddab777 commit 2b6b878

File tree

4 files changed

+238
-0
lines changed

4 files changed

+238
-0
lines changed

cell.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1745,3 +1745,18 @@ func shiftCell(val string, dCol, dRow int) string {
17451745
}
17461746
return strings.Join(parts, ":")
17471747
}
1748+
1749+
// SetCellEmbedFormula provides a function to set EMBED formula on the cell.
1750+
// This is a convenience function for setting object embedding formulas in Excel cells.
1751+
// For example:
1752+
//
1753+
// err := f.SetCellEmbedFormula("Sheet1", "A1", "Package")
1754+
//
1755+
// This will set the formula =EMBED("Package","") in cell A1.
1756+
func (f *File) SetCellEmbedFormula(sheet, cell, objectType string) error {
1757+
if objectType == "" {
1758+
objectType = "Package"
1759+
}
1760+
formula := fmt.Sprintf("EMBED(\"%s\",\"\")", objectType)
1761+
return f.SetCellFormula(sheet, cell, formula)
1762+
}

picture.go

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ package excelize
1414
import (
1515
"bytes"
1616
"encoding/xml"
17+
"fmt"
1718
"image"
1819
"io"
1920
"os"
@@ -1018,3 +1019,155 @@ func (f *File) getDispImages(sheet, cell string) ([]Picture, error) {
10181019
}
10191020
return pics, err
10201021
}
1022+
1023+
// EmbeddedObjectOptions defines the format set of embedded object.
1024+
type EmbeddedObjectOptions struct {
1025+
AltText string
1026+
PrintObject *bool
1027+
Locked *bool
1028+
ObjectType string // Default is "Package"
1029+
}
1030+
1031+
// AddEmbeddedObject provides a method to embed a file as an object in a cell.
1032+
// The embedded object will be accessible through Excel's EMBED formula.
1033+
// Supported object types include "Package" for general files. For example:
1034+
//
1035+
// package main
1036+
//
1037+
// import (
1038+
// "fmt"
1039+
// "os"
1040+
//
1041+
// "github.com/xuri/excelize/v2"
1042+
// )
1043+
//
1044+
// func main() {
1045+
// f := excelize.NewFile()
1046+
// defer func() {
1047+
// if err := f.Close(); err != nil {
1048+
// fmt.Println(err)
1049+
// }
1050+
// }()
1051+
//
1052+
// // Read file to embed
1053+
// file, err := os.ReadFile("document.pdf")
1054+
// if err != nil {
1055+
// fmt.Println(err)
1056+
// return
1057+
// }
1058+
//
1059+
// // Add embedded object
1060+
// if err := f.AddEmbeddedObject("Sheet1", "A1", "document.pdf", file,
1061+
// &excelize.EmbeddedObjectOptions{
1062+
// ObjectType: "Package",
1063+
// AltText: "Embedded PDF Document",
1064+
// }); err != nil {
1065+
// fmt.Println(err)
1066+
// return
1067+
// }
1068+
//
1069+
// if err := f.SaveAs("Book1.xlsx"); err != nil {
1070+
// fmt.Println(err)
1071+
// }
1072+
// }
1073+
func (f *File) AddEmbeddedObject(sheet, cell, filename string, file []byte, opts *EmbeddedObjectOptions) error {
1074+
if opts == nil {
1075+
opts = &EmbeddedObjectOptions{
1076+
ObjectType: "Package",
1077+
PrintObject: boolPtr(true),
1078+
Locked: boolPtr(true),
1079+
}
1080+
}
1081+
if opts.ObjectType == "" {
1082+
opts.ObjectType = "Package"
1083+
}
1084+
if opts.PrintObject == nil {
1085+
opts.PrintObject = boolPtr(true)
1086+
}
1087+
if opts.Locked == nil {
1088+
opts.Locked = boolPtr(true)
1089+
}
1090+
1091+
// Add embedded object to package
1092+
objPath := f.addEmbeddedObject(file, filename)
1093+
1094+
// Add relationships
1095+
sheetXMLPath, _ := f.getSheetXMLPath(sheet)
1096+
sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetXMLPath, "xl/worksheets/") + ".rels"
1097+
rID := f.addRels(sheetRels, SourceRelationshipOLEObject, "../"+objPath, "")
1098+
1099+
// Set EMBED formula in cell
1100+
formula := fmt.Sprintf("EMBED(\"%s\",\"\")", opts.ObjectType)
1101+
if err := f.SetCellFormula(sheet, cell, formula); err != nil {
1102+
return err
1103+
}
1104+
1105+
// Add OLE object to worksheet
1106+
ws, err := f.workSheetReader(sheet)
1107+
if err != nil {
1108+
return err
1109+
}
1110+
1111+
if ws.OleObjects == nil {
1112+
ws.OleObjects = &xlsxInnerXML{}
1113+
}
1114+
1115+
// Create OLE object XML content
1116+
oleObjectXML := fmt.Sprintf(`<oleObject progId="Package" dvAspect="DVASPECT_ICON" link="false" oleUpdate="OLEUPDATE_ONCALL" autoLoad="false" shapeId="1025" r:id="rId%d"/>`, rID)
1117+
if ws.OleObjects.Content == "" {
1118+
ws.OleObjects.Content = oleObjectXML
1119+
} else {
1120+
ws.OleObjects.Content += oleObjectXML
1121+
}
1122+
1123+
// Add content type for embedded object
1124+
return f.addContentTypePartEmbeddedObject()
1125+
}
1126+
1127+
// addEmbeddedObject adds embedded object file to the package and returns the path.
1128+
func (f *File) addEmbeddedObject(file []byte, filename string) string {
1129+
count := f.countEmbeddedObjects()
1130+
objPath := "embeddings/oleObject" + strconv.Itoa(count+1) + ".bin"
1131+
f.Pkg.Store("xl/"+objPath, file)
1132+
return objPath
1133+
}
1134+
1135+
// countEmbeddedObjects counts the number of embedded objects in the package.
1136+
func (f *File) countEmbeddedObjects() int {
1137+
count := 0
1138+
f.Pkg.Range(func(k, v interface{}) bool {
1139+
if strings.Contains(k.(string), "xl/embeddings/oleObject") {
1140+
count++
1141+
}
1142+
return true
1143+
})
1144+
return count
1145+
}
1146+
1147+
// addContentTypePartEmbeddedObject adds content type for embedded objects.
1148+
func (f *File) addContentTypePartEmbeddedObject() error {
1149+
content, err := f.contentTypesReader()
1150+
if err != nil {
1151+
return err
1152+
}
1153+
content.mu.Lock()
1154+
defer content.mu.Unlock()
1155+
1156+
// Check if bin extension already exists
1157+
var binExists bool
1158+
for _, v := range content.Defaults {
1159+
if v.Extension == "bin" {
1160+
binExists = true
1161+
break
1162+
}
1163+
}
1164+
1165+
if !binExists {
1166+
content.Defaults = append(content.Defaults, xlsxDefault{
1167+
Extension: "bin",
1168+
ContentType: ContentTypeOLEObject,
1169+
})
1170+
}
1171+
1172+
return nil
1173+
}

picture_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -615,3 +615,70 @@ func TestGetImageCells(t *testing.T) {
615615
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
616616
assert.NoError(t, f.Close())
617617
}
618+
619+
func TestAddEmbeddedObject(t *testing.T) {
620+
f := NewFile()
621+
defer func() {
622+
assert.NoError(t, f.Close())
623+
}()
624+
625+
// Test data for embedding
626+
testData := []byte("This is a test document content")
627+
628+
// Test adding embedded object with default options
629+
err := f.AddEmbeddedObject("Sheet1", "A1", "test.txt", testData, nil)
630+
assert.NoError(t, err)
631+
632+
// Verify the EMBED formula was set
633+
formula, err := f.GetCellFormula("Sheet1", "A1")
634+
assert.NoError(t, err)
635+
assert.Equal(t, `EMBED("Package","")`, formula)
636+
637+
// Test adding embedded object with custom options
638+
err = f.AddEmbeddedObject("Sheet1", "B1", "document.pdf", testData,
639+
&EmbeddedObjectOptions{
640+
ObjectType: "Package",
641+
AltText: "Embedded PDF Document",
642+
})
643+
assert.NoError(t, err)
644+
645+
// Verify the second EMBED formula
646+
formula, err = f.GetCellFormula("Sheet1", "B1")
647+
assert.NoError(t, err)
648+
assert.Equal(t, `EMBED("Package","")`, formula)
649+
650+
// Test with invalid sheet name
651+
err = f.AddEmbeddedObject("", "A1", "test.txt", testData, nil)
652+
assert.EqualError(t, err, ErrSheetNameBlank.Error())
653+
654+
// Test with invalid cell reference
655+
err = f.AddEmbeddedObject("Sheet1", "", "test.txt", testData, nil)
656+
assert.EqualError(t, err, `cannot convert cell "" to coordinates: invalid cell name ""`)
657+
}
658+
659+
func TestSetCellEmbedFormula(t *testing.T) {
660+
f := NewFile()
661+
defer func() {
662+
assert.NoError(t, f.Close())
663+
}()
664+
665+
// Test setting EMBED formula with default Package type
666+
err := f.SetCellEmbedFormula("Sheet1", "A1", "")
667+
assert.NoError(t, err)
668+
669+
formula, err := f.GetCellFormula("Sheet1", "A1")
670+
assert.NoError(t, err)
671+
assert.Equal(t, `EMBED("Package","")`, formula)
672+
673+
// Test setting EMBED formula with custom object type
674+
err = f.SetCellEmbedFormula("Sheet1", "B1", "Document")
675+
assert.NoError(t, err)
676+
677+
formula, err = f.GetCellFormula("Sheet1", "B1")
678+
assert.NoError(t, err)
679+
assert.Equal(t, `EMBED("Document","")`, formula)
680+
681+
// Test with invalid sheet name
682+
err = f.SetCellEmbedFormula("", "A1", "Package")
683+
assert.EqualError(t, err, ErrSheetNameBlank.Error())
684+
}

templates.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ const (
6262
ContentTypeTemplateMacro = "application/vnd.ms-excel.template.macroEnabled.main+xml"
6363
ContentTypeVBA = "application/vnd.ms-office.vbaProject"
6464
ContentTypeVML = "application/vnd.openxmlformats-officedocument.vmlDrawing"
65+
ContentTypeOLEObject = "application/vnd.openxmlformats-officedocument.oleObject"
6566
NameSpaceDrawingMLMain = "http://schemas.openxmlformats.org/drawingml/2006/main"
6667
NameSpaceDublinCore = "http://purl.org/dc/elements/1.1/"
6768
NameSpaceDublinCoreMetadataInitiative = "http://purl.org/dc/dcmitype/"
@@ -88,6 +89,8 @@ const (
8889
SourceRelationshipTable = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/table"
8990
SourceRelationshipVBAProject = "http://schemas.microsoft.com/office/2006/relationships/vbaProject"
9091
SourceRelationshipWorkSheet = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"
92+
SourceRelationshipOLEObject = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/oleObject"
93+
SourceRelationshipPackage = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/package"
9194
StrictNameSpaceDocumentPropertiesVariantTypes = "http://purl.oclc.org/ooxml/officeDocument/docPropsVTypes"
9295
StrictNameSpaceDrawingMLMain = "http://purl.oclc.org/ooxml/drawingml/main"
9396
StrictNameSpaceExtendedProperties = "http://purl.oclc.org/ooxml/officeDocument/extendedProperties"

0 commit comments

Comments
 (0)