diff --git a/excelize.go b/excelize.go
index ea8926a3b2..7836451859 100644
--- a/excelize.go
+++ b/excelize.go
@@ -672,3 +672,14 @@ func (f *File) getRichValueWebImageRelationships(rID string) *xlsxRelationship {
}
return nil
}
+
+// richValueStructureReader provides a function to get the pointer to the structure after
+// deserialization of xl/richData/rdrichvaluestructure.xml.
+func (f *File) richValueStructureReader() (*xlsxRichValueStructuresData, error) {
+ var richValueStruct xlsxRichValueStructuresData
+ if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLRdRichValueStructurePart)))).
+ Decode(&richValueStruct); err != nil && err != io.EOF {
+ return &richValueStruct, err
+ }
+ return &richValueStruct, nil
+}
diff --git a/picture.go b/picture.go
index 5f7a7d3c53..f402e69192 100644
--- a/picture.go
+++ b/picture.go
@@ -31,10 +31,24 @@ type PictureInsertType byte
const (
PictureInsertTypePlaceOverCells PictureInsertType = iota
PictureInsertTypePlaceInCell
- PictureInsertTypeIMAGE
+ PictureInsertTypeIMAGE // created directly by formula (ex, =IMAGE), A type of PlaceInCell
PictureInsertTypeDISPIMG
)
+// CalcOrigin: indicates how the rich value was created.
+const (
+ CalcOriginNone = string(iota + '0')
+ CalcOriginFormula // RichValue created directly by formula (ex, =IMAGE)
+ CalcOriginComplexFormula
+ CalcOriginDotNotation
+ CalcOriginReference
+ CalcOriginStandalone // Standalone RichValue directly stored in a cell without formula dependency (copy/paste as value or LocalImageValue)
+ CalcOriginStandaloneDecorative // Standalone RichValue created from the alt text pane after selecting "decorative"
+ CalcOriginNested
+ CalcOriginJSApi
+ CalcOriginPythonResult
+)
+
// parseGraphicOptions provides a function to parse the format settings of
// the picture with default value.
func parseGraphicOptions(opts *GraphicOptions) *GraphicOptions {
@@ -943,6 +957,33 @@ func (f *File) getImageCellRel(c *xlsxC, pic *Picture) (*xlsxRelationship, error
return r, err
}
rv := richValue.Rv[richValueIdx].V
+ rs := richValue.Rv[richValueIdx].S
+
+ richValueStructure, err := f.richValueStructureReader()
+ if err != nil {
+ return r, err
+ }
+ if rs < len(richValueStructure.S) {
+ pic.InsertType = PictureInsertTypePlaceInCell
+ for idx, key := range richValueStructure.S[rs].K {
+ if idx >= len(rv) {
+ break // invalid key
+ }
+ switch key.N {
+ case "_rvRel:LocalImageIdentifier", "WebImageIdentifier":
+ r, err = f.getRichDataRichValueRel(rv[idx])
+ case "Text":
+ pic.Format.AltText = rv[idx]
+ case "CalcOrigin":
+ // cell image inserted by IMAGE formula function
+ if rv[idx] == CalcOriginFormula {
+ pic.InsertType = PictureInsertTypeIMAGE
+ }
+ }
+ }
+ return r, err
+ }
+ // fallback, there is no valid struct definition
if len(rv) == 2 && rv[1] == "5" {
pic.InsertType = PictureInsertTypePlaceInCell
return f.getRichDataRichValueRel(rv[0])
diff --git a/picture_test.go b/picture_test.go
index 28042a99be..394c6df952 100644
--- a/picture_test.go
+++ b/picture_test.go
@@ -500,6 +500,11 @@ func TestGetCellImages(t *testing.T) {
})
return f
}
+ addStructure := func(f *File) *File {
+ f.Pkg.Store(defaultXMLRdRichValueStructurePart, []byte(`
+ `))
+ return f
+ }
f = prepareWorkbook()
pics, err := f.GetPictures("Sheet1", "A1")
assert.NoError(t, err)
@@ -509,6 +514,17 @@ func TestGetCellImages(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, []string{"A1"}, cells)
+ f = addStructure(prepareWorkbook())
+ // Test get the cell images with rich value struct
+ pics, err = f.GetPictures("Sheet1", "A1")
+ assert.NoError(t, err)
+ assert.Equal(t, 1, len(pics))
+ assert.Equal(t, PictureInsertTypePlaceInCell, pics[0].InsertType)
+ cells, err = f.GetPictureCells("Sheet1")
+ assert.NoError(t, err)
+ assert.Equal(t, []string{"A1"}, cells)
+
+ f = prepareWorkbook()
// Test get the cell images without image relationships parts
f.Relationships.Delete(defaultXMLRdRichValueRelRels)
f.Pkg.Store(defaultXMLRdRichValueRelRels, []byte(fmt.Sprintf(``, SourceRelationshipHyperLink)))
@@ -605,6 +621,18 @@ func TestGetCellImages(t *testing.T) {
f.Pkg.Store(defaultXMLRdRichValuePart, []byte(`100`))
_, err = f.GetPictures("Sheet1", "A1")
assert.EqualError(t, err, "strconv.Atoi: parsing \"\": invalid syntax")
+
+ f = addStructure(prepareWorkbook())
+ // Test get the cell images with no valid definition and fallback old-style
+ f.Pkg.Store(defaultXMLRdRichValueStructurePart, []byte(``))
+ pics, err = f.GetPictures("Sheet1", "A1")
+ assert.NoError(t, err)
+ assert.Equal(t, 1, len(pics))
+
+ // Test get the cell images with unsupported charset rich value
+ f.Pkg.Store(defaultXMLRdRichValueStructurePart, MacintoshCyrillicCharset)
+ _, err = f.GetPictures("Sheet1", "A1")
+ assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
}
func TestGetImageCells(t *testing.T) {
@@ -615,3 +643,33 @@ func TestGetImageCells(t *testing.T) {
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
assert.NoError(t, f.Close())
}
+
+func TestGetCellImagesAndAltText(t *testing.T) {
+ f, err := OpenFile(filepath.Join("test", "CellImage.xlsx"))
+ assert.NoError(t, err)
+ type imageType struct {
+ cell string
+ insertType PictureInsertType
+ altText string
+ }
+ want := []imageType{
+ {"B1", PictureInsertTypePlaceInCell, "Smiling alarm clock face"},
+ {"B2", PictureInsertTypePlaceInCell, ""},
+ {"B3", PictureInsertTypePlaceInCell, "Bullseye outline"},
+ {"B4", PictureInsertTypeIMAGE, ""},
+ {"B5", PictureInsertTypeIMAGE, "other alt_text"},
+
+ {"D1", PictureInsertTypePlaceInCell, "Smiling alarm clock face"},
+ {"D2", PictureInsertTypePlaceInCell, ""},
+ {"D3", PictureInsertTypePlaceInCell, "Bullseye outline"},
+ {"D4", PictureInsertTypePlaceInCell, ""},
+ {"D5", PictureInsertTypePlaceInCell, "other alt_text"},
+ }
+ for _, c := range want {
+ p, err := f.GetPictures("Sheet1", c.cell)
+ assert.NoError(t, err)
+ assert.Equal(t, 1, len(p))
+ assert.Equal(t, c.insertType, p[0].InsertType, c.cell)
+ }
+ assert.NoError(t, f.Close())
+}
diff --git a/templates.go b/templates.go
index bd027b8a30..f62a7171de 100644
--- a/templates.go
+++ b/templates.go
@@ -309,6 +309,7 @@ const (
defaultXMLRdRichValueRelRels = "xl/richData/_rels/richValueRel.xml.rels"
defaultXMLRdRichValueWebImagePart = "xl/richData/rdRichValueWebImage.xml"
defaultXMLRdRichValueWebImagePartRels = "xl/richData/_rels/rdRichValueWebImage.xml.rels"
+ defaultXMLRdRichValueStructurePart = "xl/richData/rdrichvaluestructure.xml"
)
// IndexedColorMapping is the table of default mappings from indexed color value
diff --git a/test/CellImage.xlsx b/test/CellImage.xlsx
new file mode 100644
index 0000000000..3d6401fe70
Binary files /dev/null and b/test/CellImage.xlsx differ
diff --git a/xmlMetaData.go b/xmlMetaData.go
index 90542fbd39..0297acbbbd 100644
--- a/xmlMetaData.go
+++ b/xmlMetaData.go
@@ -115,3 +115,25 @@ type xlsxWebImageSupportingRichData struct {
MoreImagesAddress xlsxExternalReference `xml:"moreImagesAddress"`
Blip xlsxExternalReference `xml:"blip"`
}
+
+// xlsxRichValueStructuresData directly maps the rvStructures element that specifies
+// rich value data.
+type xlsxRichValueStructuresData struct {
+ XMLName xml.Name `xml:"rvStructures"`
+ Count string `xml:"count,attr"`
+ S []xlsxRichValueStructure `xml:"s"`
+}
+
+// xlsxRichValueStructure directly maps the RichValueStructure element that specifies rich
+// value data.
+type xlsxRichValueStructure struct {
+ T string `xml:"t,attr"`
+ K []xlsxRichValueKey `xml:"k"`
+}
+
+// xlsxRichValueKey directly maps the rich value key element that specifies rich value
+// data.
+type xlsxRichValueKey struct {
+ N string `xml:"n,attr"`
+ T string `xml:"t,attr"`
+}