@@ -14,6 +14,7 @@ package excelize
1414import (
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+ }
0 commit comments