diff --git a/excelize.go b/excelize.go index 1bf0248..8b1b763 100644 --- a/excelize.go +++ b/excelize.go @@ -601,6 +601,17 @@ func (f *File) metadataReader() (*xlsxMetadata, error) { return &mataData, nil } +// richValueReader provides a function to get the pointer to the structure after +// deserialization of xl/richData/richvalue.xml. +func (f *File) richValueReader() (*xlsxRichValueData, error) { + var richValue xlsxRichValueData + if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLRichDataRichValue)))). + Decode(&richValue); err != nil && err != io.EOF { + return &richValue, err + } + return &richValue, 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) { diff --git a/picture.go b/picture.go index d784079..a52bbc3 100644 --- a/picture.go +++ b/picture.go @@ -450,7 +450,8 @@ func (f *File) addMedia(file []byte, ext string) string { // GetPictures provides a function to get picture meta info and raw content // embed in spreadsheet by given worksheet and cell name. This function // returns the image contents as []byte data types. This function is -// concurrency safe. For example: +// concurrency safe. Note that, this function doesn't support getting cell image +// inserted by IMAGE formula function currently. For example: // // f, err := excelize.OpenFile("Book1.xlsx") // if err != nil { @@ -506,7 +507,8 @@ func (f *File) GetPictures(sheet, cell string) ([]Picture, error) { } // GetPictureCells returns all picture cell references in a worksheet by a -// specific worksheet name. +// specific worksheet name. Note that, this function doesn't support getting +// cell image inserted by IMAGE formula function currently. func (f *File) GetPictureCells(sheet string) ([]string, error) { f.mu.Lock() ws, err := f.workSheetReader(sheet) @@ -790,7 +792,7 @@ func (f *File) cellImagesReader() (*decodeCellImages, error) { return f.DecodeCellImages, nil } -// getImageCells returns all the Microsoft 365 cell images and the Kingsoft WPS +// getImageCells returns all the cell images and the Kingsoft WPS // Office embedded image cells reference by given worksheet name. func (f *File) getImageCells(sheet string) ([]string, error) { var ( @@ -823,7 +825,29 @@ func (f *File) getImageCells(sheet string) ([]string, error) { return cells, err } -// getImageCellRel returns the Microsoft 365 cell image relationship. +// getImageCellRichValueIdx returns index of the cell image rich value by given +// cell value meta index and meta blocks. +func (f *File) getImageCellRichValueIdx(vm uint, blocks *xlsxMetadataBlocks) (int, error) { + richValueIdx := blocks.Bk[vm-1].Rc[0].V + richValue, err := f.richValueReader() + if err != nil { + return -1, err + } + if richValueIdx >= len(richValue.Rv) { + return -1, err + } + rv := richValue.Rv[richValueIdx].V + if len(rv) != 2 || rv[1] != "5" { + return -1, err + } + richValueRelIdx, err := strconv.Atoi(rv[0]) + if err != nil { + return -1, err + } + return richValueRelIdx, err +} + +// getImageCellRel returns the cell image relationship. func (f *File) getImageCellRel(c *xlsxC) (*xlsxRelationship, error) { var r *xlsxRelationship if c.Vm == nil || c.V != formulaErrorVALUE { @@ -837,21 +861,25 @@ func (f *File) getImageCellRel(c *xlsxC) (*xlsxRelationship, error) { if vmd == nil || int(*c.Vm) > len(vmd.Bk) || len(vmd.Bk[*c.Vm-1].Rc) == 0 { return r, err } + richValueRelIdx, err := f.getImageCellRichValueIdx(*c.Vm, vmd) + if err != nil || richValueRelIdx == -1 { + 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) { + if richValueRelIdx >= len(richValueRel.Rels) { return r, err } - rID := richValueRel.Rels[vmd.Bk[*c.Vm-1].Rc[0].V].ID + rID := richValueRel.Rels[richValueRelIdx].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 +// getCellImages provides a function to get the 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) { diff --git a/picture_test.go b/picture_test.go index 7d08b8d..bb90634 100644 --- a/picture_test.go +++ b/picture_test.go @@ -452,17 +452,22 @@ func TestGetCellImages(t *testing.T) { 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)}}}, - }}, - }) + // Test get the cell images + prepareWorkbook := func() *File { + f := NewFile() + assert.NoError(t, f.AddPicture("Sheet1", "A1", filepath.Join("test", "images", "excel.png"), nil)) + f.Pkg.Store(defaultXMLMetadata, []byte(``)) + f.Pkg.Store(defaultXMLRichDataRichValue, []byte(`05`)) + 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)}}}, + }}, + }) + return f + } + f = prepareWorkbook() pics, err := f.GetPictures("Sheet1", "A1") assert.NoError(t, err) assert.Equal(t, 1, len(pics)) @@ -471,41 +476,64 @@ func TestGetCellImages(t *testing.T) { assert.NoError(t, err) assert.Equal(t, []string{"A1"}, cells) - // Test get the Microsoft 365 cell images without image relationships parts + // Test get the 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 + // Test get the 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 + // Test get the 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 + // Test get the 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 + // Test get the 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 + // Test get the 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 + // Test get the 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) + + f = prepareWorkbook() + // Test get the cell images with empty image cell rich value + f.Pkg.Store(defaultXMLRichDataRichValue, []byte(`5`)) + pics, err = f.GetPictures("Sheet1", "A1") + assert.EqualError(t, err, "strconv.Atoi: parsing \"\": invalid syntax") + assert.Empty(t, pics) + // Test get the cell images without image cell rich value + f.Pkg.Store(defaultXMLRichDataRichValue, []byte(`01`)) + pics, err = f.GetPictures("Sheet1", "A1") + assert.NoError(t, err) + assert.Empty(t, pics) + // Test get the cell images with unsupported charset rich value + f.Pkg.Store(defaultXMLRichDataRichValue, MacintoshCyrillicCharset) + _, err = f.GetPictures("Sheet1", "A1") + assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") + + f = prepareWorkbook() + // Test get the cell images with invalid rich value index + f.Pkg.Store(defaultXMLMetadata, []byte(``)) + pics, err = f.GetPictures("Sheet1", "A1") + assert.NoError(t, err) + assert.Empty(t, pics) } func TestGetImageCells(t *testing.T) { diff --git a/templates.go b/templates.go index e097e81..7d72e3b 100644 --- a/templates.go +++ b/templates.go @@ -280,6 +280,7 @@ const ( defaultXMLPathVolatileDeps = "xl/volatileDependencies.xml" defaultXMLPathWorkbook = "xl/workbook.xml" defaultXMLPathWorkbookRels = "xl/_rels/workbook.xml.rels" + defaultXMLRichDataRichValue = "xl/richData/rdrichvalue.xml" defaultXMLRichDataRichValueRel = "xl/richData/richValueRel.xml" defaultXMLRichDataRichValueRelRels = "xl/richData/_rels/richValueRel.xml.rels" ) diff --git a/xmlMetaData.go b/xmlMetaData.go index e8cf7db..b3f97b6 100644 --- a/xmlMetaData.go +++ b/xmlMetaData.go @@ -68,6 +68,23 @@ type xlsxMetadataRecord struct { V int `xml:"v,attr"` } +// xlsxRichValueData directly maps the rvData element that specifies rich value +// data. +type xlsxRichValueData struct { + XMLName xml.Name `xml:"rvData"` + Count int `xml:"count,attr,omitempty"` + Rv []xlsxRichValue `xml:"rv"` + ExtLst *xlsxInnerXML `xml:"extLst"` +} + +// xlsxRichValue directly maps the rv element that specifies rich value data +// information for a single rich value +type xlsxRichValue struct { + S int `xml:"s,attr"` + V []string `xml:"v"` + Fb *xlsxInnerXML `xml:"fb"` +} + // xlsxRichValueRels directly maps the richValueRels element. This element that // specifies a list of rich value relationships. type xlsxRichValueRels struct {