Skip to content

Commit ffad7ae

Browse files
authored
Support get rich data value rels index from rich value part (#1866)
1 parent 5e500f5 commit ffad7ae

File tree

5 files changed

+110
-25
lines changed

5 files changed

+110
-25
lines changed

excelize.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,17 @@ func (f *File) metadataReader() (*xlsxMetadata, error) {
601601
return &mataData, nil
602602
}
603603

604+
// richValueReader provides a function to get the pointer to the structure after
605+
// deserialization of xl/richData/richvalue.xml.
606+
func (f *File) richValueReader() (*xlsxRichValueData, error) {
607+
var richValue xlsxRichValueData
608+
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLRichDataRichValue)))).
609+
Decode(&richValue); err != nil && err != io.EOF {
610+
return &richValue, err
611+
}
612+
return &richValue, nil
613+
}
614+
604615
// richValueRelReader provides a function to get the pointer to the structure
605616
// after deserialization of xl/richData/richValueRel.xml.
606617
func (f *File) richValueRelReader() (*xlsxRichValueRels, error) {

picture.go

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -450,7 +450,8 @@ func (f *File) addMedia(file []byte, ext string) string {
450450
// GetPictures provides a function to get picture meta info and raw content
451451
// embed in spreadsheet by given worksheet and cell name. This function
452452
// returns the image contents as []byte data types. This function is
453-
// concurrency safe. For example:
453+
// concurrency safe. Note that, this function doesn't support getting cell image
454+
// inserted by IMAGE formula function currently. For example:
454455
//
455456
// f, err := excelize.OpenFile("Book1.xlsx")
456457
// if err != nil {
@@ -506,7 +507,8 @@ func (f *File) GetPictures(sheet, cell string) ([]Picture, error) {
506507
}
507508

508509
// GetPictureCells returns all picture cell references in a worksheet by a
509-
// specific worksheet name.
510+
// specific worksheet name. Note that, this function doesn't support getting
511+
// cell image inserted by IMAGE formula function currently.
510512
func (f *File) GetPictureCells(sheet string) ([]string, error) {
511513
f.mu.Lock()
512514
ws, err := f.workSheetReader(sheet)
@@ -790,7 +792,7 @@ func (f *File) cellImagesReader() (*decodeCellImages, error) {
790792
return f.DecodeCellImages, nil
791793
}
792794

793-
// getImageCells returns all the Microsoft 365 cell images and the Kingsoft WPS
795+
// getImageCells returns all the cell images and the Kingsoft WPS
794796
// Office embedded image cells reference by given worksheet name.
795797
func (f *File) getImageCells(sheet string) ([]string, error) {
796798
var (
@@ -823,7 +825,29 @@ func (f *File) getImageCells(sheet string) ([]string, error) {
823825
return cells, err
824826
}
825827

826-
// getImageCellRel returns the Microsoft 365 cell image relationship.
828+
// getImageCellRichValueIdx returns index of the cell image rich value by given
829+
// cell value meta index and meta blocks.
830+
func (f *File) getImageCellRichValueIdx(vm uint, blocks *xlsxMetadataBlocks) (int, error) {
831+
richValueIdx := blocks.Bk[vm-1].Rc[0].V
832+
richValue, err := f.richValueReader()
833+
if err != nil {
834+
return -1, err
835+
}
836+
if richValueIdx >= len(richValue.Rv) {
837+
return -1, err
838+
}
839+
rv := richValue.Rv[richValueIdx].V
840+
if len(rv) != 2 || rv[1] != "5" {
841+
return -1, err
842+
}
843+
richValueRelIdx, err := strconv.Atoi(rv[0])
844+
if err != nil {
845+
return -1, err
846+
}
847+
return richValueRelIdx, err
848+
}
849+
850+
// getImageCellRel returns the cell image relationship.
827851
func (f *File) getImageCellRel(c *xlsxC) (*xlsxRelationship, error) {
828852
var r *xlsxRelationship
829853
if c.Vm == nil || c.V != formulaErrorVALUE {
@@ -837,21 +861,25 @@ func (f *File) getImageCellRel(c *xlsxC) (*xlsxRelationship, error) {
837861
if vmd == nil || int(*c.Vm) > len(vmd.Bk) || len(vmd.Bk[*c.Vm-1].Rc) == 0 {
838862
return r, err
839863
}
864+
richValueRelIdx, err := f.getImageCellRichValueIdx(*c.Vm, vmd)
865+
if err != nil || richValueRelIdx == -1 {
866+
return r, err
867+
}
840868
richValueRel, err := f.richValueRelReader()
841869
if err != nil {
842870
return r, err
843871
}
844-
if vmd.Bk[*c.Vm-1].Rc[0].V >= len(richValueRel.Rels) {
872+
if richValueRelIdx >= len(richValueRel.Rels) {
845873
return r, err
846874
}
847-
rID := richValueRel.Rels[vmd.Bk[*c.Vm-1].Rc[0].V].ID
875+
rID := richValueRel.Rels[richValueRelIdx].ID
848876
if r = f.getRichDataRichValueRelRelationships(rID); r != nil && r.Type != SourceRelationshipImage {
849877
return nil, err
850878
}
851879
return r, err
852880
}
853881

854-
// getCellImages provides a function to get the Microsoft 365 cell images and
882+
// getCellImages provides a function to get the cell images and
855883
// the Kingsoft WPS Office embedded cell images by given worksheet name and cell
856884
// reference.
857885
func (f *File) getCellImages(sheet, cell string) ([]Picture, error) {

picture_test.go

Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -452,17 +452,22 @@ func TestGetCellImages(t *testing.T) {
452452
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
453453
assert.NoError(t, f.Close())
454454

455-
// Test get the Microsoft 365 cell images
456-
f = NewFile()
457-
assert.NoError(t, f.AddPicture("Sheet1", "A1", filepath.Join("test", "images", "excel.png"), nil))
458-
f.Pkg.Store(defaultXMLMetadata, []byte(`<metadata><valueMetadata count="1"><bk><rc t="1" v="0"/></bk></valueMetadata></metadata>`))
459-
f.Pkg.Store(defaultXMLRichDataRichValueRel, []byte(`<richValueRels><rel r:id="rId1"/></richValueRels>`))
460-
f.Pkg.Store(defaultXMLRichDataRichValueRelRels, []byte(fmt.Sprintf(`<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="%s" Target="../media/image1.png"/></Relationships>`, SourceRelationshipImage)))
461-
f.Sheet.Store("xl/worksheets/sheet1.xml", &xlsxWorksheet{
462-
SheetData: xlsxSheetData{Row: []xlsxRow{
463-
{R: 1, C: []xlsxC{{R: "A1", T: "e", V: formulaErrorVALUE, Vm: uintPtr(1)}}},
464-
}},
465-
})
455+
// Test get the cell images
456+
prepareWorkbook := func() *File {
457+
f := NewFile()
458+
assert.NoError(t, f.AddPicture("Sheet1", "A1", filepath.Join("test", "images", "excel.png"), nil))
459+
f.Pkg.Store(defaultXMLMetadata, []byte(`<metadata><valueMetadata count="1"><bk><rc t="1" v="0"/></bk></valueMetadata></metadata>`))
460+
f.Pkg.Store(defaultXMLRichDataRichValue, []byte(`<rvData count="1"><rv s="0"><v>0</v><v>5</v></rv></rvData>`))
461+
f.Pkg.Store(defaultXMLRichDataRichValueRel, []byte(`<richValueRels><rel r:id="rId1"/></richValueRels>`))
462+
f.Pkg.Store(defaultXMLRichDataRichValueRelRels, []byte(fmt.Sprintf(`<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="%s" Target="../media/image1.png"/></Relationships>`, SourceRelationshipImage)))
463+
f.Sheet.Store("xl/worksheets/sheet1.xml", &xlsxWorksheet{
464+
SheetData: xlsxSheetData{Row: []xlsxRow{
465+
{R: 1, C: []xlsxC{{R: "A1", T: "e", V: formulaErrorVALUE, Vm: uintPtr(1)}}},
466+
}},
467+
})
468+
return f
469+
}
470+
f = prepareWorkbook()
466471
pics, err := f.GetPictures("Sheet1", "A1")
467472
assert.NoError(t, err)
468473
assert.Equal(t, 1, len(pics))
@@ -471,41 +476,64 @@ func TestGetCellImages(t *testing.T) {
471476
assert.NoError(t, err)
472477
assert.Equal(t, []string{"A1"}, cells)
473478

474-
// Test get the Microsoft 365 cell images without image relationships parts
479+
// Test get the cell images without image relationships parts
475480
f.Relationships.Delete(defaultXMLRichDataRichValueRelRels)
476481
f.Pkg.Store(defaultXMLRichDataRichValueRelRels, []byte(fmt.Sprintf(`<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="%s" Target="../media/image1.png"/></Relationships>`, SourceRelationshipHyperLink)))
477482
pics, err = f.GetPictures("Sheet1", "A1")
478483
assert.NoError(t, err)
479484
assert.Empty(t, pics)
480-
// Test get the Microsoft 365 cell images with unsupported charset rich data rich value relationships
485+
// Test get the cell images with unsupported charset rich data rich value relationships
481486
f.Relationships.Delete(defaultXMLRichDataRichValueRelRels)
482487
f.Pkg.Store(defaultXMLRichDataRichValueRelRels, MacintoshCyrillicCharset)
483488
pics, err = f.GetPictures("Sheet1", "A1")
484489
assert.NoError(t, err)
485490
assert.Empty(t, pics)
486-
// Test get the Microsoft 365 cell images with unsupported charset rich data rich value
491+
// Test get the cell images with unsupported charset rich data rich value
487492
f.Pkg.Store(defaultXMLRichDataRichValueRel, MacintoshCyrillicCharset)
488493
_, err = f.GetPictures("Sheet1", "A1")
489494
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
490-
// Test get the Microsoft 365 image cells without block of metadata records
495+
// Test get the image cells without block of metadata records
491496
cells, err = f.GetPictureCells("Sheet1")
492497
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
493498
assert.Empty(t, cells)
494-
// Test get the Microsoft 365 cell images with rich data rich value relationships
499+
// Test get the cell images with rich data rich value relationships
495500
f.Pkg.Store(defaultXMLMetadata, []byte(`<metadata><valueMetadata count="1"><bk><rc t="1" v="0"/></bk></valueMetadata></metadata>`))
496501
f.Pkg.Store(defaultXMLRichDataRichValueRel, []byte(`<richValueRels/>`))
497502
pics, err = f.GetPictures("Sheet1", "A1")
498503
assert.NoError(t, err)
499504
assert.Empty(t, pics)
500-
// Test get the Microsoft 365 cell images with unsupported charset meta data
505+
// Test get the cell images with unsupported charset meta data
501506
f.Pkg.Store(defaultXMLMetadata, MacintoshCyrillicCharset)
502507
_, err = f.GetPictures("Sheet1", "A1")
503508
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
504-
// Test get the Microsoft 365 cell images without block of metadata records
509+
// Test get the cell images without block of metadata records
505510
f.Pkg.Store(defaultXMLMetadata, []byte(`<metadata><valueMetadata/></metadata>`))
506511
pics, err = f.GetPictures("Sheet1", "A1")
507512
assert.NoError(t, err)
508513
assert.Empty(t, pics)
514+
515+
f = prepareWorkbook()
516+
// Test get the cell images with empty image cell rich value
517+
f.Pkg.Store(defaultXMLRichDataRichValue, []byte(`<rvData count="1"><rv s="0"><v></v><v>5</v></rv></rvData>`))
518+
pics, err = f.GetPictures("Sheet1", "A1")
519+
assert.EqualError(t, err, "strconv.Atoi: parsing \"\": invalid syntax")
520+
assert.Empty(t, pics)
521+
// Test get the cell images without image cell rich value
522+
f.Pkg.Store(defaultXMLRichDataRichValue, []byte(`<rvData count="1"><rv s="0"><v>0</v><v>1</v></rv></rvData>`))
523+
pics, err = f.GetPictures("Sheet1", "A1")
524+
assert.NoError(t, err)
525+
assert.Empty(t, pics)
526+
// Test get the cell images with unsupported charset rich value
527+
f.Pkg.Store(defaultXMLRichDataRichValue, MacintoshCyrillicCharset)
528+
_, err = f.GetPictures("Sheet1", "A1")
529+
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
530+
531+
f = prepareWorkbook()
532+
// Test get the cell images with invalid rich value index
533+
f.Pkg.Store(defaultXMLMetadata, []byte(`<metadata><valueMetadata count="1"><bk><rc t="1" v="1"/></bk></valueMetadata></metadata>`))
534+
pics, err = f.GetPictures("Sheet1", "A1")
535+
assert.NoError(t, err)
536+
assert.Empty(t, pics)
509537
}
510538

511539
func TestGetImageCells(t *testing.T) {

templates.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,7 @@ const (
280280
defaultXMLPathVolatileDeps = "xl/volatileDependencies.xml"
281281
defaultXMLPathWorkbook = "xl/workbook.xml"
282282
defaultXMLPathWorkbookRels = "xl/_rels/workbook.xml.rels"
283+
defaultXMLRichDataRichValue = "xl/richData/rdrichvalue.xml"
283284
defaultXMLRichDataRichValueRel = "xl/richData/richValueRel.xml"
284285
defaultXMLRichDataRichValueRelRels = "xl/richData/_rels/richValueRel.xml.rels"
285286
)

xmlMetaData.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,23 @@ type xlsxMetadataRecord struct {
6868
V int `xml:"v,attr"`
6969
}
7070

71+
// xlsxRichValueData directly maps the rvData element that specifies rich value
72+
// data.
73+
type xlsxRichValueData struct {
74+
XMLName xml.Name `xml:"rvData"`
75+
Count int `xml:"count,attr,omitempty"`
76+
Rv []xlsxRichValue `xml:"rv"`
77+
ExtLst *xlsxInnerXML `xml:"extLst"`
78+
}
79+
80+
// xlsxRichValue directly maps the rv element that specifies rich value data
81+
// information for a single rich value
82+
type xlsxRichValue struct {
83+
S int `xml:"s,attr"`
84+
V []string `xml:"v"`
85+
Fb *xlsxInnerXML `xml:"fb"`
86+
}
87+
7188
// xlsxRichValueRels directly maps the richValueRels element. This element that
7289
// specifies a list of rich value relationships.
7390
type xlsxRichValueRels struct {

0 commit comments

Comments
 (0)