Skip to content

This closes #2157, add support parsing rdrichvaluestructure.xml #2162

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

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions excelize.go
Original file line number Diff line number Diff line change
Expand Up @@ -668,3 +668,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
}
43 changes: 42 additions & 1 deletion picture.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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])
Expand Down
58 changes: 58 additions & 0 deletions picture_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,11 @@ func TestGetCellImages(t *testing.T) {
})
return f
}
addStructure := func(f *File) *File {
f.Pkg.Store(defaultXMLRdRichValueStructurePart, []byte(`<rvStructures xmlns="http://schemas.microsoft.com/office/spreadsheetml/2017/richdata" count="1">
<s t="_localImage"><k n="_rvRel:LocalImageIdentifier" t="i"/><k n="CalcOrigin" t="i"/><k n="Text" t="s"/></s></rvStructures>`))
return f
}
f = prepareWorkbook()
pics, err := f.GetPictures("Sheet1", "A1")
assert.NoError(t, err)
Expand All @@ -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(`<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="%s" Target="../media/image1.png"/></Relationships>`, SourceRelationshipHyperLink)))
Expand Down Expand Up @@ -605,6 +621,18 @@ func TestGetCellImages(t *testing.T) {
f.Pkg.Store(defaultXMLRdRichValuePart, []byte(`<rvData count="1"><rv s="1"><v></v><v>1</v><v>0</v><v>0</v></rv></rvData>`))
_, 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(`<rvStructures xmlns="http://schemas.microsoft.com/office/spreadsheetml/2017/richdata" count="1"></rvStructures>`))
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) {
Expand All @@ -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())
}
1 change: 1 addition & 0 deletions templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,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
Expand Down
Binary file added test/CellImage.xlsx
Binary file not shown.
22 changes: 22 additions & 0 deletions xmlMetaData.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
}