diff --git a/excelize.go b/excelize.go
index 264101874709df7280c1333c317074fb72b3fbba..1bf0248b3fae05ceb02c1a3221c03561fcaabfa1 100644
--- a/excelize.go
+++ b/excelize.go
@@ -589,3 +589,41 @@ func (f *File) setContentTypePartProjectExtensions(contentType string) error {
}
return err
}
+
+// metadataReader provides a function to get the pointer to the structure
+// after deserialization of xl/metadata.xml.
+func (f *File) metadataReader() (*xlsxMetadata, error) {
+ var mataData xlsxMetadata
+ if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLMetadata)))).
+ Decode(&mataData); err != nil && err != io.EOF {
+ return &mataData, err
+ }
+ return &mataData, nil
+}
+
+// richValueRelReader provides a function to get the pointer to the structure
+// after deserialization of xl/richData/richValueRel.xml.
+func (f *File) richValueRelReader() (*xlsxRichValueRels, error) {
+ var richValueRels xlsxRichValueRels
+ if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLRichDataRichValueRel)))).
+ Decode(&richValueRels); err != nil && err != io.EOF {
+ return &richValueRels, err
+ }
+ return &richValueRels, nil
+}
+
+// getRichDataRichValueRelRelationships provides a function to get drawing
+// relationships from xl/richData/_rels/richValueRel.xml.rels by given
+// relationship ID.
+func (f *File) getRichDataRichValueRelRelationships(rID string) *xlsxRelationship {
+ if rels, _ := f.relsReader(defaultXMLRichDataRichValueRelRels); rels != nil {
+ rels.mu.Lock()
+ defer rels.mu.Unlock()
+ for _, v := range rels.Relationships {
+ if v.ID == rID {
+ return &v
+ }
+ }
+ }
+ return nil
+}
diff --git a/picture.go b/picture.go
index 8b006f8e3ea83623af8b741fecc638737a3f8207..e6410dc9821ba87ecd0933f383dd0e8e58c64d6c 100644
--- a/picture.go
+++ b/picture.go
@@ -497,13 +497,13 @@ func (f *File) GetPictureCells(sheet string) ([]string, error) {
}
f.mu.Unlock()
if ws.Drawing == nil {
- return f.getEmbeddedImageCells(sheet)
+ return f.getImageCells(sheet)
}
target := f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID)
drawingXML := strings.TrimPrefix(strings.ReplaceAll(target, "..", "xl"), "/")
drawingRelationships := strings.ReplaceAll(
strings.ReplaceAll(target, "../drawings", "xl/drawings/_rels"), ".xml", ".xml.rels")
- embeddedImageCells, err := f.getEmbeddedImageCells(sheet)
+ embeddedImageCells, err := f.getImageCells(sheet)
if err != nil {
return nil, err
}
@@ -771,9 +771,9 @@ func (f *File) cellImagesReader() (*decodeCellImages, error) {
return f.DecodeCellImages, nil
}
-// getEmbeddedImageCells returns all the Kingsoft WPS Office embedded image
-// cells reference by given worksheet name.
-func (f *File) getEmbeddedImageCells(sheet string) ([]string, error) {
+// getImageCells returns all the Microsoft 365 cell images and the Kingsoft WPS
+// Office embedded image cells reference by given worksheet name.
+func (f *File) getImageCells(sheet string) ([]string, error) {
var (
err error
cells []string
@@ -791,14 +791,73 @@ func (f *File) getEmbeddedImageCells(sheet string) ([]string, error) {
}
cells = append(cells, c.R)
}
+ r, err := f.getImageCellRel(&c)
+ if err != nil {
+ return cells, err
+ }
+ if r != nil {
+ cells = append(cells, c.R)
+ }
+
}
}
return cells, err
}
-// getCellImages provides a function to get the Kingsoft WPS Office embedded
-// cell images by given worksheet name and cell reference.
+// getImageCellRel returns the Microsoft 365 cell image relationship.
+func (f *File) getImageCellRel(c *xlsxC) (*xlsxRelationship, error) {
+ var r *xlsxRelationship
+ if c.Vm == nil || c.V != formulaErrorVALUE {
+ return r, nil
+ }
+ metaData, err := f.metadataReader()
+ if err != nil {
+ return r, err
+ }
+ vmd := metaData.ValueMetadata
+ if vmd == nil || int(*c.Vm) > len(vmd.Bk) || len(vmd.Bk[*c.Vm-1].Rc) == 0 {
+ return r, err
+ }
+ richValueRel, err := f.richValueRelReader()
+ if err != nil {
+ return r, err
+ }
+ if vmd.Bk[*c.Vm-1].Rc[0].V >= len(richValueRel.Rels) {
+ return r, err
+ }
+ rID := richValueRel.Rels[vmd.Bk[*c.Vm-1].Rc[0].V].ID
+ if r = f.getRichDataRichValueRelRelationships(rID); r != nil && r.Type != SourceRelationshipImage {
+ return nil, err
+ }
+ return r, err
+}
+
+// getCellImages provides a function to get the Microsoft 365 cell images and
+// the Kingsoft WPS Office embedded cell images by given worksheet name and cell
+// reference.
func (f *File) getCellImages(sheet, cell string) ([]Picture, error) {
+ pics, err := f.getDispImages(sheet, cell)
+ if err != nil {
+ return pics, err
+ }
+ _, err = f.getCellStringFunc(sheet, cell, func(x *xlsxWorksheet, c *xlsxC) (string, bool, error) {
+ r, err := f.getImageCellRel(c)
+ if err != nil || r == nil {
+ return "", true, err
+ }
+ pic := Picture{Extension: filepath.Ext(r.Target), Format: &GraphicOptions{}}
+ if buffer, _ := f.Pkg.Load(strings.TrimPrefix(strings.ReplaceAll(r.Target, "..", "xl"), "/")); buffer != nil {
+ pic.File = buffer.([]byte)
+ pics = append(pics, pic)
+ }
+ return "", true, nil
+ })
+ return pics, err
+}
+
+// getDispImages provides a function to get the Kingsoft WPS Office embedded
+// cell images by given worksheet name and cell reference.
+func (f *File) getDispImages(sheet, cell string) ([]Picture, error) {
formula, err := f.GetCellFormula(sheet, cell)
if err != nil {
return nil, err
diff --git a/picture_test.go b/picture_test.go
index 376f3997c69ed67b671b257557f738fd3704dece..dfd5f623daa0b16d1cae16be30f1be125fd1be87 100644
--- a/picture_test.go
+++ b/picture_test.go
@@ -448,13 +448,67 @@ func TestGetCellImages(t *testing.T) {
_, err := f.getCellImages("Sheet1", "A1")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
assert.NoError(t, f.Close())
+
+ // Test get the Microsoft 365 cell images
+ f = NewFile()
+ assert.NoError(t, f.AddPicture("Sheet1", "A1", filepath.Join("test", "images", "excel.png"), nil))
+ f.Pkg.Store(defaultXMLMetadata, []byte(``))
+ f.Pkg.Store(defaultXMLRichDataRichValueRel, []byte(``))
+ f.Pkg.Store(defaultXMLRichDataRichValueRelRels, []byte(fmt.Sprintf(``, SourceRelationshipImage)))
+ f.Sheet.Store("xl/worksheets/sheet1.xml", &xlsxWorksheet{
+ SheetData: xlsxSheetData{Row: []xlsxRow{
+ {R: 1, C: []xlsxC{{R: "A1", T: "e", V: formulaErrorVALUE, Vm: uintPtr(1)}}},
+ }},
+ })
+ pics, err := f.GetPictures("Sheet1", "A1")
+ assert.NoError(t, err)
+ assert.Equal(t, 1, len(pics))
+ cells, err := f.GetPictureCells("Sheet1")
+ assert.NoError(t, err)
+ assert.Equal(t, []string{"A1"}, cells)
+
+ // Test get the Microsoft 365 cell images without image relationships parts
+ f.Relationships.Delete(defaultXMLRichDataRichValueRelRels)
+ f.Pkg.Store(defaultXMLRichDataRichValueRelRels, []byte(fmt.Sprintf(``, SourceRelationshipHyperLink)))
+ pics, err = f.GetPictures("Sheet1", "A1")
+ assert.NoError(t, err)
+ assert.Empty(t, pics)
+ // Test get the Microsoft 365 cell images with unsupported charset rich data rich value relationships
+ f.Relationships.Delete(defaultXMLRichDataRichValueRelRels)
+ f.Pkg.Store(defaultXMLRichDataRichValueRelRels, MacintoshCyrillicCharset)
+ pics, err = f.GetPictures("Sheet1", "A1")
+ assert.NoError(t, err)
+ assert.Empty(t, pics)
+ // Test get the Microsoft 365 cell images with unsupported charset rich data rich value
+ f.Pkg.Store(defaultXMLRichDataRichValueRel, MacintoshCyrillicCharset)
+ _, err = f.GetPictures("Sheet1", "A1")
+ assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
+ // Test get the Microsoft 365 image cells without block of metadata records
+ cells, err = f.GetPictureCells("Sheet1")
+ assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
+ assert.Empty(t, cells)
+ // Test get the Microsoft 365 cell images with rich data rich value relationships
+ f.Pkg.Store(defaultXMLMetadata, []byte(``))
+ f.Pkg.Store(defaultXMLRichDataRichValueRel, []byte(``))
+ pics, err = f.GetPictures("Sheet1", "A1")
+ assert.NoError(t, err)
+ assert.Empty(t, pics)
+ // Test get the Microsoft 365 cell images with unsupported charset meta data
+ f.Pkg.Store(defaultXMLMetadata, MacintoshCyrillicCharset)
+ _, err = f.GetPictures("Sheet1", "A1")
+ assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
+ // Test get the Microsoft 365 cell images without block of metadata records
+ f.Pkg.Store(defaultXMLMetadata, []byte(``))
+ pics, err = f.GetPictures("Sheet1", "A1")
+ assert.NoError(t, err)
+ assert.Empty(t, pics)
}
-func TestGetEmbeddedImageCells(t *testing.T) {
+func TestGetImageCells(t *testing.T) {
f := NewFile()
f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.Pkg.Store("xl/worksheets/sheet1.xml", MacintoshCyrillicCharset)
- _, err := f.getEmbeddedImageCells("Sheet1")
+ _, err := f.getImageCells("Sheet1")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
assert.NoError(t, f.Close())
}
diff --git a/templates.go b/templates.go
index 0f21be54bcaf76b7ade9263c03dee3fe76dc6050..e097e818057578c0fe3cf3024527e5be344b59ac 100644
--- a/templates.go
+++ b/templates.go
@@ -266,19 +266,22 @@ var supportedChartDataLabelsPosition = map[ChartType][]ChartDataLabelPositionTyp
}
const (
- defaultTempFileSST = "sharedStrings"
- defaultXMLPathCalcChain = "xl/calcChain.xml"
- defaultXMLPathCellImages = "xl/cellimages.xml"
- defaultXMLPathCellImagesRels = "xl/_rels/cellimages.xml.rels"
- defaultXMLPathContentTypes = "[Content_Types].xml"
- defaultXMLPathDocPropsApp = "docProps/app.xml"
- defaultXMLPathDocPropsCore = "docProps/core.xml"
- defaultXMLPathSharedStrings = "xl/sharedStrings.xml"
- defaultXMLPathStyles = "xl/styles.xml"
- defaultXMLPathTheme = "xl/theme/theme1.xml"
- defaultXMLPathVolatileDeps = "xl/volatileDependencies.xml"
- defaultXMLPathWorkbook = "xl/workbook.xml"
- defaultXMLPathWorkbookRels = "xl/_rels/workbook.xml.rels"
+ defaultTempFileSST = "sharedStrings"
+ defaultXMLMetadata = "xl/metadata.xml"
+ defaultXMLPathCalcChain = "xl/calcChain.xml"
+ defaultXMLPathCellImages = "xl/cellimages.xml"
+ defaultXMLPathCellImagesRels = "xl/_rels/cellimages.xml.rels"
+ defaultXMLPathContentTypes = "[Content_Types].xml"
+ defaultXMLPathDocPropsApp = "docProps/app.xml"
+ defaultXMLPathDocPropsCore = "docProps/core.xml"
+ defaultXMLPathSharedStrings = "xl/sharedStrings.xml"
+ defaultXMLPathStyles = "xl/styles.xml"
+ defaultXMLPathTheme = "xl/theme/theme1.xml"
+ defaultXMLPathVolatileDeps = "xl/volatileDependencies.xml"
+ defaultXMLPathWorkbook = "xl/workbook.xml"
+ defaultXMLPathWorkbookRels = "xl/_rels/workbook.xml.rels"
+ defaultXMLRichDataRichValueRel = "xl/richData/richValueRel.xml"
+ defaultXMLRichDataRichValueRelRels = "xl/richData/_rels/richValueRel.xml.rels"
)
// IndexedColorMapping is the table of default mappings from indexed color value
diff --git a/xmlMetaData.go b/xmlMetaData.go
new file mode 100644
index 0000000000000000000000000000000000000000..e8cf7dbb8c44ea8e4757624d4e8572eb8ec1ba4d
--- /dev/null
+++ b/xmlMetaData.go
@@ -0,0 +1,83 @@
+// Copyright 2016 - 2024 The excelize Authors. All rights reserved. Use of
+// this source code is governed by a BSD-style license that can be found in
+// the LICENSE file.
+//
+// Package excelize providing a set of functions that allow you to write to and
+// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
+// writing spreadsheet documents generated by Microsoft Excelâ„¢ 2007 and later.
+// Supports complex components by high compatibility, and provided streaming
+// API for generating or reading data from a worksheet with huge amounts of
+// data. This library needs Go version 1.18 or later.
+
+package excelize
+
+import "encoding/xml"
+
+// xlsxMetadata directly maps the metadata element. A cell in a spreadsheet
+// application can have metadata associated with it. Metadata is just a set of
+// additional properties about the particular cell, and this metadata is stored
+// in the metadata xml part. There are two types of metadata: cell metadata and
+// value metadata. Cell metadata contains information about the cell itself,
+// and this metadata can be carried along with the cell as it moves
+// (insert, shift, copy/paste, merge, unmerge, etc). Value metadata is
+// information about the value of a particular cell. Value metadata properties
+// can be propagated along with the value as it is referenced in formulas.
+type xlsxMetadata struct {
+ XMLName xml.Name `xml:"metadata"`
+ MetadataTypes *xlsxInnerXML `xml:"metadataTypes"`
+ MetadataStrings *xlsxInnerXML `xml:"metadataStrings"`
+ MdxMetadata *xlsxInnerXML `xml:"mdxMetadata"`
+ FutureMetadata []xlsxFutureMetadata `xml:"futureMetadata"`
+ CellMetadata *xlsxMetadataBlocks `xml:"cellMetadata"`
+ ValueMetadata *xlsxMetadataBlocks `xml:"valueMetadata"`
+ ExtLst *xlsxInnerXML `xml:"extLst"`
+}
+
+// xlsxFutureMetadata directly maps the futureMetadata element. This element
+// represents future metadata information.
+type xlsxFutureMetadata struct {
+ Bk []xlsxFutureMetadataBlock `xml:"bk"`
+ ExtLst *xlsxInnerXML `xml:"extLst"`
+}
+
+// xlsxFutureMetadataBlock directly maps the kb element. This element represents
+// a block of future metadata information. This is a location for storing
+// feature extension information.
+type xlsxFutureMetadataBlock struct {
+ ExtLst *xlsxInnerXML `xml:"extLst"`
+}
+
+// xlsxMetadataBlocks directly maps the metadata element. This element
+// represents cell metadata information. Cell metadata is information metadata
+// about a specific cell, and it stays tied to that cell position.
+type xlsxMetadataBlocks struct {
+ Count int `xml:"count,attr,omitempty"`
+ Bk []xlsxMetadataBlock `xml:"bk"`
+}
+
+// xlsxMetadataBlock directly maps the bk element. This element represents a
+// block of metadata records.
+type xlsxMetadataBlock struct {
+ Rc []xlsxMetadataRecord `xml:"rc"`
+}
+
+// xlsxMetadataRecord directly maps the rc element. This element represents a
+// reference to a specific metadata record.
+type xlsxMetadataRecord struct {
+ T int `xml:"t,attr"`
+ V int `xml:"v,attr"`
+}
+
+// xlsxRichValueRels directly maps the richValueRels element. This element that
+// specifies a list of rich value relationships.
+type xlsxRichValueRels struct {
+ XMLName xml.Name `xml:"richValueRels"`
+ Rels []xlsxRichValueRelRelationship `xml:"rel"`
+ ExtLst *xlsxInnerXML `xml:"extLst"`
+}
+
+// xlsxRichValueRelRelationship directly maps the rel element. This element
+// specifies a relationship for a rich value property.
+type xlsxRichValueRelRelationship struct {
+ ID string `xml:"id,attr"`
+}