Skip to content

Commit 4bb5800

Browse files
shcabinshcabin
authored andcommitted
This closes #2157, add support parsing rdrichvaluestructure.xml
1 parent d434acd commit 4bb5800

File tree

6 files changed

+166
-1
lines changed

6 files changed

+166
-1
lines changed

excelize.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -668,3 +668,14 @@ func (f *File) getRichValueWebImageRelationships(rID string) *xlsxRelationship {
668668
}
669669
return nil
670670
}
671+
672+
// richValueStructureReader provides a function to get the pointer to the structure after
673+
// deserialization of xl/richData/rdrichvaluestructure.xml.
674+
func (f *File) richValueStructureReader() (*xlsxRichValueStructuresData, error) {
675+
var richValueStruct xlsxRichValueStructuresData
676+
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLRdRichValueStructurePart)))).
677+
Decode(&richValueStruct); err != nil && err != io.EOF {
678+
return &richValueStruct, err
679+
}
680+
return &richValueStruct, nil
681+
}

picture.go

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,24 @@ type PictureInsertType byte
3131
const (
3232
PictureInsertTypePlaceOverCells PictureInsertType = iota
3333
PictureInsertTypePlaceInCell
34-
PictureInsertTypeIMAGE
34+
PictureInsertTypeIMAGE // created directly by formula (ex, =IMAGE), A type of PlaceInCell
3535
PictureInsertTypeDISPIMG
3636
)
3737

38+
// CalcOrigin: indicates how the rich value was created.
39+
const (
40+
CalcOriginNone = string(iota + '0')
41+
CalcOriginFormula // RichValue created directly by formula (ex, =IMAGE)
42+
CalcOriginComplexFormula
43+
CalcOriginDotNotation
44+
CalcOriginReference
45+
CalcOriginStandalone // Standalone RichValue directly stored in a cell without formula dependency (copy/paste as value or LocalImageValue)
46+
CalcOriginStandaloneDecorative // Standalone RichValue created from the alt text pane after selecting "decorative"
47+
CalcOriginNested
48+
CalcOriginJSApi
49+
CalcOriginPythonResult
50+
)
51+
3852
// parseGraphicOptions provides a function to parse the format settings of
3953
// the picture with default value.
4054
func parseGraphicOptions(opts *GraphicOptions) *GraphicOptions {
@@ -943,6 +957,33 @@ func (f *File) getImageCellRel(c *xlsxC, pic *Picture) (*xlsxRelationship, error
943957
return r, err
944958
}
945959
rv := richValue.Rv[richValueIdx].V
960+
rs := richValue.Rv[richValueIdx].S
961+
962+
richValueStructure, err := f.richValueStructureReader()
963+
if err != nil {
964+
return r, err
965+
}
966+
if rs < len(richValueStructure.S) {
967+
pic.InsertType = PictureInsertTypePlaceInCell
968+
for idx, key := range richValueStructure.S[rs].K {
969+
if idx >= len(rv) {
970+
break // invalid key
971+
}
972+
switch key.N {
973+
case "_rvRel:LocalImageIdentifier", "WebImageIdentifier":
974+
r, err = f.getRichDataRichValueRel(rv[idx])
975+
case "Text":
976+
pic.Format.AltText = rv[idx]
977+
case "CalcOrigin":
978+
// cell image inserted by IMAGE formula function
979+
if rv[idx] == CalcOriginFormula {
980+
pic.InsertType = PictureInsertTypeIMAGE
981+
}
982+
}
983+
}
984+
return r, err
985+
}
986+
// fallback, there is no valid struct definition
946987
if len(rv) == 2 && rv[1] == "5" {
947988
pic.InsertType = PictureInsertTypePlaceInCell
948989
return f.getRichDataRichValueRel(rv[0])

picture_test.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,11 @@ func TestGetCellImages(t *testing.T) {
500500
})
501501
return f
502502
}
503+
addStructure := func(f *File) *File {
504+
f.Pkg.Store(defaultXMLRdRichValueStructurePart, []byte(`<rvStructures xmlns="http://schemas.microsoft.com/office/spreadsheetml/2017/richdata" count="1">
505+
<s t="_localImage"><k n="_rvRel:LocalImageIdentifier" t="i"/><k n="CalcOrigin" t="i"/><k n="Text" t="s"/></s></rvStructures>`))
506+
return f
507+
}
503508
f = prepareWorkbook()
504509
pics, err := f.GetPictures("Sheet1", "A1")
505510
assert.NoError(t, err)
@@ -509,6 +514,17 @@ func TestGetCellImages(t *testing.T) {
509514
assert.NoError(t, err)
510515
assert.Equal(t, []string{"A1"}, cells)
511516

517+
f = addStructure(prepareWorkbook())
518+
// Test get the cell images with rich value struct
519+
pics, err = f.GetPictures("Sheet1", "A1")
520+
assert.NoError(t, err)
521+
assert.Equal(t, 1, len(pics))
522+
assert.Equal(t, PictureInsertTypePlaceInCell, pics[0].InsertType)
523+
cells, err = f.GetPictureCells("Sheet1")
524+
assert.NoError(t, err)
525+
assert.Equal(t, []string{"A1"}, cells)
526+
527+
f = prepareWorkbook()
512528
// Test get the cell images without image relationships parts
513529
f.Relationships.Delete(defaultXMLRdRichValueRelRels)
514530
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)))
@@ -605,6 +621,18 @@ func TestGetCellImages(t *testing.T) {
605621
f.Pkg.Store(defaultXMLRdRichValuePart, []byte(`<rvData count="1"><rv s="1"><v></v><v>1</v><v>0</v><v>0</v></rv></rvData>`))
606622
_, err = f.GetPictures("Sheet1", "A1")
607623
assert.EqualError(t, err, "strconv.Atoi: parsing \"\": invalid syntax")
624+
625+
f = addStructure(prepareWorkbook())
626+
// Test get the cell images with no valid definition and fallback old-style
627+
f.Pkg.Store(defaultXMLRdRichValueStructurePart, []byte(`<rvStructures xmlns="http://schemas.microsoft.com/office/spreadsheetml/2017/richdata" count="1"></rvStructures>`))
628+
pics, err = f.GetPictures("Sheet1", "A1")
629+
assert.NoError(t, err)
630+
assert.Equal(t, 1, len(pics))
631+
632+
// Test get the cell images with unsupported charset rich value
633+
f.Pkg.Store(defaultXMLRdRichValueStructurePart, MacintoshCyrillicCharset)
634+
_, err = f.GetPictures("Sheet1", "A1")
635+
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
608636
}
609637

610638
func TestGetImageCells(t *testing.T) {
@@ -615,3 +643,65 @@ func TestGetImageCells(t *testing.T) {
615643
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
616644
assert.NoError(t, f.Close())
617645
}
646+
647+
func TestGetCellImagesAndAltText(t *testing.T) {
648+
f, err := OpenFile(filepath.Join("test", "CellImage.xlsx"))
649+
assert.NoError(t, err)
650+
type imageType struct {
651+
cell string
652+
insertType PictureInsertType
653+
altText string
654+
}
655+
want := []imageType{
656+
{"B1", PictureInsertTypePlaceInCell, "Smiling alarm clock face"},
657+
{"B2", PictureInsertTypePlaceInCell, ""},
658+
{"B3", PictureInsertTypePlaceInCell, "Bullseye outline"},
659+
{"B4", PictureInsertTypeIMAGE, ""},
660+
{"B5", PictureInsertTypeIMAGE, "other alt_text"},
661+
662+
{"D1", PictureInsertTypePlaceInCell, "Smiling alarm clock face"},
663+
{"D2", PictureInsertTypePlaceInCell, ""},
664+
{"D3", PictureInsertTypePlaceInCell, "Bullseye outline"},
665+
{"D4", PictureInsertTypePlaceInCell, ""},
666+
{"D5", PictureInsertTypePlaceInCell, "other alt_text"},
667+
}
668+
for _, c := range want {
669+
p, err := f.GetPictures("Sheet1", c.cell)
670+
assert.NoError(t, err)
671+
assert.Equal(t, 1, len(p))
672+
assert.Equal(t, c.insertType, p[0].InsertType, c.cell)
673+
}
674+
assert.NoError(t, f.Close())
675+
}
676+
677+
func TestGetImageCells3(t *testing.T) {
678+
f, _ := OpenFile("cell_image2.xlsx")
679+
for _, str := range []string{"E5", "E6", "E7"} {
680+
p, err := f.GetPictures("PriceList", str)
681+
assert.NoError(t, err)
682+
assert.NotNil(t, p)
683+
for _, x := range p {
684+
assert.Equal(t, PictureInsertTypePlaceInCell, x.InsertType)
685+
t.Logf("str:%v, p:%v, InsertType:%v Format:%v", str, x.Extension, x.InsertType, x.Format)
686+
}
687+
}
688+
p, err := f.GetPictures("PriceList", "E8")
689+
assert.NoError(t, err)
690+
assert.NotEqual(t, nil, p)
691+
for _, x := range p {
692+
assert.Equal(t, PictureInsertTypeIMAGE, x.InsertType)
693+
t.Logf("E8 p:%v, InsertType:%v Format:%v", x.Extension, x.InsertType, x.Format)
694+
}
695+
696+
for _, str := range []string{"D11", "D12", "D13", "D14"} {
697+
p, err := f.GetPictures("BillOfMaterials", str)
698+
assert.NoError(t, err)
699+
assert.NotNil(t, p)
700+
for _, x := range p {
701+
assert.Equal(t, PictureInsertTypePlaceInCell, x.InsertType)
702+
t.Logf("str:%v, p:%v, InsertType:%v Format:%v", str, x.Extension, x.InsertType, x.Format)
703+
}
704+
}
705+
706+
assert.NoError(t, f.Close())
707+
}

templates.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,7 @@ const (
294294
defaultXMLRdRichValueRelRels = "xl/richData/_rels/richValueRel.xml.rels"
295295
defaultXMLRdRichValueWebImagePart = "xl/richData/rdRichValueWebImage.xml"
296296
defaultXMLRdRichValueWebImagePartRels = "xl/richData/_rels/rdRichValueWebImage.xml.rels"
297+
defaultXMLRdRichValueStructurePart = "xl/richData/rdrichvaluestructure.xml"
297298
)
298299

299300
// IndexedColorMapping is the table of default mappings from indexed color value

test/CellImage.xlsx

126 KB
Binary file not shown.

xmlMetaData.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,25 @@ type xlsxWebImageSupportingRichData struct {
115115
MoreImagesAddress xlsxExternalReference `xml:"moreImagesAddress"`
116116
Blip xlsxExternalReference `xml:"blip"`
117117
}
118+
119+
// xlsxRichValueStructuresData directly maps the rvStructures element that specifies
120+
// rich value data.
121+
type xlsxRichValueStructuresData struct {
122+
XMLName xml.Name `xml:"rvStructures"`
123+
Count string `xml:"count,attr"`
124+
S []xlsxRichValueStructure `xml:"s"`
125+
}
126+
127+
// xlsxRichValueStructure directly maps the RichValueStructure element that specifies rich
128+
// value data.
129+
type xlsxRichValueStructure struct {
130+
T string `xml:"t,attr"`
131+
K []xlsxRichValueKey `xml:"k"`
132+
}
133+
134+
// xlsxRichValueKey directly maps the rich value key element that specifies rich value
135+
// data.
136+
type xlsxRichValueKey struct {
137+
N string `xml:"n,attr"`
138+
T string `xml:"t,attr"`
139+
}

0 commit comments

Comments
 (0)