Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support get rich data value rels index from rich value part #1866

Merged
merged 1 commit into from
Mar 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 11 additions & 0 deletions excelize.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
42 changes: 35 additions & 7 deletions picture.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 (
Expand Down Expand Up @@ -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 {
Expand All @@ -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) {
Expand Down
64 changes: 46 additions & 18 deletions picture_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(`<metadata><valueMetadata count="1"><bk><rc t="1" v="0"/></bk></valueMetadata></metadata>`))
f.Pkg.Store(defaultXMLRichDataRichValueRel, []byte(`<richValueRels><rel r:id="rId1"/></richValueRels>`))
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)))
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(`<metadata><valueMetadata count="1"><bk><rc t="1" v="0"/></bk></valueMetadata></metadata>`))
f.Pkg.Store(defaultXMLRichDataRichValue, []byte(`<rvData count="1"><rv s="0"><v>0</v><v>5</v></rv></rvData>`))
f.Pkg.Store(defaultXMLRichDataRichValueRel, []byte(`<richValueRels><rel r:id="rId1"/></richValueRels>`))
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)))
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))
Expand All @@ -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(`<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="%s" Target="../media/image1.png"/></Relationships>`, 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(`<metadata><valueMetadata count="1"><bk><rc t="1" v="0"/></bk></valueMetadata></metadata>`))
f.Pkg.Store(defaultXMLRichDataRichValueRel, []byte(`<richValueRels/>`))
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(`<metadata><valueMetadata/></metadata>`))
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(`<rvData count="1"><rv s="0"><v></v><v>5</v></rv></rvData>`))
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(`<rvData count="1"><rv s="0"><v>0</v><v>1</v></rv></rvData>`))
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(`<metadata><valueMetadata count="1"><bk><rc t="1" v="1"/></bk></valueMetadata></metadata>`))
pics, err = f.GetPictures("Sheet1", "A1")
assert.NoError(t, err)
assert.Empty(t, pics)
}

func TestGetImageCells(t *testing.T) {
Expand Down
1 change: 1 addition & 0 deletions templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down
17 changes: 17 additions & 0 deletions xmlMetaData.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down