Skip to content

Commit 265e50a

Browse files
committed
This closes qax-os#2157, the GetPictures function support return alt text of cell images
- Get rich data value with structures to fix missing return some cell images - Fix typo for the metadataReader function - Update unit tests
1 parent 0b5dc32 commit 265e50a

File tree

5 files changed

+131
-48
lines changed

5 files changed

+131
-48
lines changed

excelize.go

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -618,19 +618,19 @@ func (f *File) setContentTypePartProjectExtensions(contentType string) error {
618618
// metadataReader provides a function to get the pointer to the structure
619619
// after deserialization of xl/metadata.xml.
620620
func (f *File) metadataReader() (*xlsxMetadata, error) {
621-
var mataData xlsxMetadata
621+
var metaData xlsxMetadata
622622
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLMetadata)))).
623-
Decode(&mataData); err != nil && err != io.EOF {
624-
return &mataData, err
623+
Decode(&metaData); err != nil && err != io.EOF {
624+
return &metaData, err
625625
}
626-
return &mataData, nil
626+
return &metaData, nil
627627
}
628628

629629
// richValueReader provides a function to get the pointer to the structure after
630630
// deserialization of xl/richData/richvalue.xml.
631631
func (f *File) richValueReader() (*xlsxRichValueData, error) {
632632
var richValue xlsxRichValueData
633-
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLRdRichValuePart)))).
633+
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLRdRichValue)))).
634634
Decode(&richValue); err != nil && err != io.EOF {
635635
return &richValue, err
636636
}
@@ -648,11 +648,22 @@ func (f *File) richValueRelReader() (*xlsxRichValueRels, error) {
648648
return &richValueRels, nil
649649
}
650650

651+
// richValueStructuresReader provides a function to get the pointer to the structure after
652+
// deserialization of xl/richData/rdrichvaluestructure.xml.
653+
func (f *File) richValueStructuresReader() (*xlsxRichValueStructures, error) {
654+
var richValueStructures xlsxRichValueStructures
655+
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLRdRichValueStructure)))).
656+
Decode(&richValueStructures); err != nil && err != io.EOF {
657+
return &richValueStructures, err
658+
}
659+
return &richValueStructures, nil
660+
}
661+
651662
// richValueWebImageReader provides a function to get the pointer to the
652663
// structure after deserialization of xl/richData/rdRichValueWebImage.xml.
653664
func (f *File) richValueWebImageReader() (*xlsxWebImagesSupportingRichData, error) {
654665
var richValueWebImages xlsxWebImagesSupportingRichData
655-
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLRdRichValueWebImagePart)))).
666+
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLRdRichValueWebImage)))).
656667
Decode(&richValueWebImages); err != nil && err != io.EOF {
657668
return &richValueWebImages, err
658669
}
@@ -677,7 +688,7 @@ func (f *File) getRichDataRichValueRelRelationships(rID string) *xlsxRelationshi
677688
// getRichValueWebImageRelationships provides a function to get relationships
678689
// from xl/richData/_rels/rdRichValueWebImage.xml.rels by given relationship ID.
679690
func (f *File) getRichValueWebImageRelationships(rID string) *xlsxRelationship {
680-
if rels, _ := f.relsReader(defaultXMLRdRichValueWebImagePartRels); rels != nil {
691+
if rels, _ := f.relsReader(defaultXMLRdRichValueWebImageRels); rels != nil {
681692
rels.mu.Lock()
682693
defer rels.mu.Unlock()
683694
for _, v := range rels.Relationships {

picture.go

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -964,7 +964,7 @@ func (f *File) getImageCells(sheet string) ([]string, error) {
964964
}
965965
cells = append(cells, c.R)
966966
}
967-
r, err := f.getImageCellRel(&c, &Picture{})
967+
r, err := f.getImageCellRel(&c, &Picture{Format: &GraphicOptions{}})
968968
if err != nil {
969969
return cells, err
970970
}
@@ -1043,19 +1043,44 @@ func (f *File) getImageCellRel(c *xlsxC, pic *Picture) (*xlsxRelationship, error
10431043
if richValueIdx >= len(richValue.Rv) {
10441044
return r, err
10451045
}
1046-
rv := richValue.Rv[richValueIdx].V
1047-
if len(rv) == 2 && rv[1] == "5" {
1046+
rv := richValue.Rv[richValueIdx]
1047+
rvStructures, err := f.richValueStructuresReader()
1048+
if err != nil {
1049+
return r, err
1050+
}
1051+
if rv.S >= len(rvStructures.S) {
1052+
return r, err
1053+
}
1054+
rvStruct := rvStructures.S[rv.S]
1055+
if len(rvStruct.K) != len(rv.V) {
1056+
return r, err
1057+
}
1058+
if idx := rvStruct.getRichDataValueIdx("Text"); idx != -1 {
1059+
pic.Format.AltText = rv.V[idx]
1060+
}
1061+
if idx := rvStruct.getRichDataValueIdx("_rvRel:LocalImageIdentifier"); idx != -1 {
10481062
pic.InsertType = PictureInsertTypePlaceInCell
1049-
return f.getRichDataRichValueRel(rv[0])
1063+
return f.getRichDataRichValueRel(rv.V[idx])
10501064
}
10511065
// cell image inserted by IMAGE formula function
1052-
if len(rv) > 3 && rv[1]+rv[2] == "10" {
1066+
if idx := rvStruct.getRichDataValueIdx("WebImageIdentifier"); idx != -1 {
10531067
pic.InsertType = PictureInsertTypeIMAGE
1054-
return f.getRichDataWebImagesRel(rv[0])
1068+
return f.getRichDataWebImagesRel(rv.V[idx])
10551069
}
10561070
return r, err
10571071
}
10581072

1073+
// getRichDataValueIdx provides a function to get the index of rich data value
1074+
// structure by given name and rich data value.
1075+
func (s *xlsxRichValueStructure) getRichDataValueIdx(n string) int {
1076+
for idx, k := range s.K {
1077+
if k.N == n {
1078+
return idx
1079+
}
1080+
}
1081+
return -1
1082+
}
1083+
10591084
// getCellImages provides a function to get the cell images and
10601085
// the Kingsoft WPS Office embedded cell images by given worksheet name and cell
10611086
// reference.

picture_test.go

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -546,9 +546,10 @@ func TestGetCellImages(t *testing.T) {
546546
f := NewFile()
547547
assert.NoError(t, f.AddPicture("Sheet1", "A1", filepath.Join("test", "images", "excel.png"), nil))
548548
f.Pkg.Store(defaultXMLMetadata, []byte(`<metadata><valueMetadata count="1"><bk><rc t="1" v="0"/></bk></valueMetadata></metadata>`))
549-
f.Pkg.Store(defaultXMLRdRichValuePart, []byte(`<rvData count="1"><rv s="0"><v>0</v><v>5</v></rv></rvData>`))
549+
f.Pkg.Store(defaultXMLRdRichValue, []byte(`<rvData count="1"><rv s="0"><v>0</v><v>5</v><v>logo</v></rv></rvData>`))
550550
f.Pkg.Store(defaultXMLRdRichValueRel, []byte(`<richValueRels><rel r:id="rId1"/></richValueRels>`))
551551
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>`, SourceRelationshipImage)))
552+
f.Pkg.Store(defaultXMLRdRichValueStructure, []byte(`<rvStructures><s t="_localImage"><k n="_rvRel:LocalImageIdentifier" t="i"/><k n="CalcOrigin" t="i"/><k n="Text" t="s"/></s></rvStructures>`))
552553
f.Sheet.Store("xl/worksheets/sheet1.xml", &xlsxWorksheet{
553554
SheetData: xlsxSheetData{Row: []xlsxRow{
554555
{R: 1, C: []xlsxC{{R: "A1", T: "e", V: formulaErrorVALUE, Vm: uintPtr(1)}}},
@@ -561,6 +562,7 @@ func TestGetCellImages(t *testing.T) {
561562
assert.NoError(t, err)
562563
assert.Equal(t, 1, len(pics))
563564
assert.Equal(t, PictureInsertTypePlaceInCell, pics[0].InsertType)
565+
assert.Equal(t, "logo", pics[0].Format.AltText)
564566
cells, err := f.GetPictureCells("Sheet1")
565567
assert.NoError(t, err)
566568
assert.Equal(t, []string{"A1"}, cells)
@@ -603,17 +605,18 @@ func TestGetCellImages(t *testing.T) {
603605

604606
f = prepareWorkbook()
605607
// Test get the cell images with empty image cell rich value
606-
f.Pkg.Store(defaultXMLRdRichValuePart, []byte(`<rvData count="1"><rv s="0"><v></v><v>5</v></rv></rvData>`))
608+
f.Pkg.Store(defaultXMLRdRichValue, []byte(`<rvData count="1"><rv s="0"><v></v><v>5</v><v>logo</v></rv></rvData>`))
607609
pics, err = f.GetPictures("Sheet1", "A1")
608610
assert.EqualError(t, err, "strconv.Atoi: parsing \"\": invalid syntax")
609611
assert.Empty(t, pics)
610612
// Test get the cell images without image cell rich value
611-
f.Pkg.Store(defaultXMLRdRichValuePart, []byte(`<rvData count="1"><rv s="0"><v>0</v><v>1</v></rv></rvData>`))
613+
f.Pkg.Store(defaultXMLRdRichValue, []byte(`<rvData count="1"><rv s="0"><v>0</v><v>1</v></rv></rvData>`))
614+
f.Pkg.Delete(defaultXMLRdRichValueStructure)
612615
pics, err = f.GetPictures("Sheet1", "A1")
613616
assert.NoError(t, err)
614617
assert.Empty(t, pics)
615618
// Test get the cell images with unsupported charset rich value
616-
f.Pkg.Store(defaultXMLRdRichValuePart, MacintoshCyrillicCharset)
619+
f.Pkg.Store(defaultXMLRdRichValue, MacintoshCyrillicCharset)
617620
_, err = f.GetPictures("Sheet1", "A1")
618621
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
619622

@@ -626,41 +629,59 @@ func TestGetCellImages(t *testing.T) {
626629

627630
f = prepareWorkbook()
628631
// Test get the cell images inserted by IMAGE formula function
629-
f.Pkg.Store(defaultXMLRdRichValuePart, []byte(`<rvData count="1"><rv s="1"><v>0</v><v>1</v><v>0</v><v>0</v></rv></rvData>`))
630-
f.Pkg.Store(defaultXMLRdRichValueWebImagePart, []byte(`<webImagesSrd xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><webImageSrd><address r:id="rId1"/><blip r:id="rId2"/></webImageSrd>
632+
f.Pkg.Store(defaultXMLRdRichValue, []byte(`<rvData count="1"><rv s="1"><v>0</v><v>1</v><v>0</v><v>0</v></rv></rvData>`))
633+
f.Pkg.Store(defaultXMLRdRichValueStructure, []byte(`<rvStructures><s t="_localImage"><k n="_rvRel:LocalImageIdentifier" t="i"/><k n="CalcOrigin" t="i"/></s><s t="_webimage"><k n="WebImageIdentifier" t="i"/><k n="CalcOrigin" t="i"/><k n="ComputedImage" t="b"/><k n="ImageSizing" t="i"/></s></rvStructures>`))
634+
f.Pkg.Store(defaultXMLRdRichValueWebImage, []byte(`<webImagesSrd xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><webImageSrd><address r:id="rId1"/><blip r:id="rId2"/></webImageSrd>
631635
</webImagesSrd>`))
632-
f.Pkg.Store(defaultXMLRdRichValueWebImagePartRels, []byte(fmt.Sprintf(`<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="%s" Target="https://github.com/xuri/excelize" TargetMode="External"/><Relationship Id="rId2" Type="%s" Target="../media/image1.png"/></Relationships>`, SourceRelationshipHyperLink, SourceRelationshipImage)))
636+
f.Pkg.Store(defaultXMLRdRichValueWebImageRels, []byte(fmt.Sprintf(`<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="%s" Target="https://github.com/xuri/excelize" TargetMode="External"/><Relationship Id="rId2" Type="%s" Target="../media/image1.png"/></Relationships>`, SourceRelationshipHyperLink, SourceRelationshipImage)))
633637
pics, err = f.GetPictures("Sheet1", "A1")
634638
assert.NoError(t, err)
635639
assert.Equal(t, 1, len(pics))
636640
assert.Equal(t, PictureInsertTypeIMAGE, pics[0].InsertType)
637641

638642
// Test get the cell images inserted by IMAGE formula function with unsupported charset web images relationships
639-
f.Relationships.Delete(defaultXMLRdRichValueWebImagePartRels)
640-
f.Pkg.Store(defaultXMLRdRichValueWebImagePartRels, MacintoshCyrillicCharset)
643+
f.Relationships.Delete(defaultXMLRdRichValueWebImageRels)
644+
f.Pkg.Store(defaultXMLRdRichValueWebImageRels, MacintoshCyrillicCharset)
641645
pics, err = f.GetPictures("Sheet1", "A1")
642646
assert.NoError(t, err)
643647
assert.Empty(t, pics)
644648

645649
// Test get the cell images inserted by IMAGE formula function without image part
646-
f.Relationships.Delete(defaultXMLRdRichValueWebImagePartRels)
647-
f.Pkg.Store(defaultXMLRdRichValueWebImagePartRels, []byte(fmt.Sprintf(`<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="%s" Target="https://github.com/xuri/excelize" TargetMode="External"/><Relationship Id="rId2" Type="%s" Target="../media/image1.png"/></Relationships>`, SourceRelationshipHyperLink, SourceRelationshipHyperLink)))
650+
f.Relationships.Delete(defaultXMLRdRichValueWebImageRels)
651+
f.Pkg.Store(defaultXMLRdRichValueWebImageRels, []byte(fmt.Sprintf(`<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="%s" Target="https://github.com/xuri/excelize" TargetMode="External"/><Relationship Id="rId2" Type="%s" Target="../media/image1.png"/></Relationships>`, SourceRelationshipHyperLink, SourceRelationshipHyperLink)))
648652
pics, err = f.GetPictures("Sheet1", "A1")
649653
assert.NoError(t, err)
650654
assert.Empty(t, pics)
651655
// Test get the cell images inserted by IMAGE formula function with unsupported charset web images part
652-
f.Pkg.Store(defaultXMLRdRichValueWebImagePart, MacintoshCyrillicCharset)
656+
f.Pkg.Store(defaultXMLRdRichValueWebImage, MacintoshCyrillicCharset)
653657
_, err = f.GetPictures("Sheet1", "A1")
654658
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
655659
// Test get the cell images inserted by IMAGE formula function with empty charset web images part
656-
f.Pkg.Store(defaultXMLRdRichValueWebImagePart, []byte(`<webImagesSrd xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" />`))
660+
f.Pkg.Store(defaultXMLRdRichValueWebImage, []byte(`<webImagesSrd xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" />`))
657661
pics, err = f.GetPictures("Sheet1", "A1")
658662
assert.NoError(t, err)
659663
assert.Empty(t, pics)
660664
// Test get the cell images inserted by IMAGE formula function with invalid rich value index
661-
f.Pkg.Store(defaultXMLRdRichValuePart, []byte(`<rvData count="1"><rv s="1"><v></v><v>1</v><v>0</v><v>0</v></rv></rvData>`))
665+
f.Pkg.Store(defaultXMLRdRichValue, []byte(`<rvData count="1"><rv s="1"><v></v><v>1</v><v>0</v><v>0</v></rv></rvData>`))
662666
_, err = f.GetPictures("Sheet1", "A1")
663667
assert.EqualError(t, err, "strconv.Atoi: parsing \"\": invalid syntax")
668+
669+
f = prepareWorkbook()
670+
// Test get the cell images inserted by IMAGE formula function with not matched rich value and structure keys
671+
f.Pkg.Store(defaultXMLRdRichValue, []byte(`<rvData count="1"><rv s="0"><v>0</v></rv></rvData>`))
672+
f.Pkg.Store(defaultXMLRdRichValueStructure, []byte(`<rvStructures><s t="_localImage"/></rvStructures>`))
673+
pics, err = f.GetPictures("Sheet1", "A1")
674+
assert.NoError(t, err)
675+
assert.Empty(t, pics)
676+
// Test get the cell images inserted by IMAGE formula function without _rvRel:WebImageIdentifier in rich value structure
677+
f.Pkg.Store(defaultXMLRdRichValueStructure, []byte(`<rvStructures><s t="_localImage"><k n="CalcOrigin" t="i"/></s></rvStructures>`))
678+
pics, err = f.GetPictures("Sheet1", "A1")
679+
assert.NoError(t, err)
680+
assert.Empty(t, pics)
681+
// Test get the cell images inserted by IMAGE formula function with unsupported charset rich value structures
682+
f.Pkg.Store(defaultXMLRdRichValueStructure, MacintoshCyrillicCharset)
683+
_, err = f.GetPictures("Sheet1", "A1")
684+
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
664685
}
665686

666687
func TestGetImageCells(t *testing.T) {

templates.go

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -288,27 +288,29 @@ var supportedChartDataLabelsPosition = map[ChartType][]ChartDataLabelPositionTyp
288288
}
289289

290290
const (
291-
defaultTempFileSST = "sharedStrings"
292-
defaultXMLMetadata = "xl/metadata.xml"
293-
defaultXMLPathCalcChain = "xl/calcChain.xml"
294-
defaultXMLPathCellImages = "xl/cellimages.xml"
295-
defaultXMLPathCellImagesRels = "xl/_rels/cellimages.xml.rels"
296-
defaultXMLPathContentTypes = "[Content_Types].xml"
297-
defaultXMLPathDocPropsApp = "docProps/app.xml"
298-
defaultXMLPathDocPropsCore = "docProps/core.xml"
299-
defaultXMLPathDocPropsCustom = "docProps/custom.xml"
300-
defaultXMLPathRels = "_rels/.rels"
301-
defaultXMLPathSharedStrings = "xl/sharedStrings.xml"
302-
defaultXMLPathStyles = "xl/styles.xml"
303-
defaultXMLPathTheme = "xl/theme/theme1.xml"
304-
defaultXMLPathVolatileDeps = "xl/volatileDependencies.xml"
305-
defaultXMLPathWorkbook = "xl/workbook.xml"
306-
defaultXMLPathWorkbookRels = "xl/_rels/workbook.xml.rels"
307-
defaultXMLRdRichValuePart = "xl/richData/rdrichvalue.xml"
308-
defaultXMLRdRichValueRel = "xl/richData/richValueRel.xml"
309-
defaultXMLRdRichValueRelRels = "xl/richData/_rels/richValueRel.xml.rels"
310-
defaultXMLRdRichValueWebImagePart = "xl/richData/rdRichValueWebImage.xml"
311-
defaultXMLRdRichValueWebImagePartRels = "xl/richData/_rels/rdRichValueWebImage.xml.rels"
291+
defaultTempFileSST = "sharedStrings"
292+
defaultXMLMetadata = "xl/metadata.xml"
293+
defaultXMLPathCalcChain = "xl/calcChain.xml"
294+
defaultXMLPathCellImages = "xl/cellimages.xml"
295+
defaultXMLPathCellImagesRels = "xl/_rels/cellimages.xml.rels"
296+
defaultXMLPathContentTypes = "[Content_Types].xml"
297+
defaultXMLPathDocPropsApp = "docProps/app.xml"
298+
defaultXMLPathDocPropsCore = "docProps/core.xml"
299+
defaultXMLPathDocPropsCustom = "docProps/custom.xml"
300+
defaultXMLPathRels = "_rels/.rels"
301+
defaultXMLPathSharedStrings = "xl/sharedStrings.xml"
302+
defaultXMLPathStyles = "xl/styles.xml"
303+
defaultXMLPathTheme = "xl/theme/theme1.xml"
304+
defaultXMLPathVolatileDeps = "xl/volatileDependencies.xml"
305+
defaultXMLPathWorkbook = "xl/workbook.xml"
306+
defaultXMLPathWorkbookRels = "xl/_rels/workbook.xml.rels"
307+
defaultXMLRdRichValue = "xl/richData/rdrichvalue.xml"
308+
defaultXMLRdRichValueRel = "xl/richData/richValueRel.xml"
309+
defaultXMLRdRichValueRelRels = "xl/richData/_rels/richValueRel.xml.rels"
310+
defaultXMLRdRichValueStructure = "xl/richData/rdrichvaluestructure.xml"
311+
defaultXMLRdRichValueTypes = "xl/richData/rdRichValueTypes.xml"
312+
defaultXMLRdRichValueWebImage = "xl/richData/rdRichValueWebImage.xml"
313+
defaultXMLRdRichValueWebImageRels = "xl/richData/_rels/rdRichValueWebImage.xml.rels"
312314
)
313315

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

xmlMetaData.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,30 @@ type xlsxRichValueRelRelationship struct {
9999
ID string `xml:"id,attr"`
100100
}
101101

102+
// xlsxRichValueStructures directly maps the rvStructures element. This element
103+
// specifies rich value structures, which contain lists of rich value keys and
104+
// the data types for the corresponding rich value data.
105+
type xlsxRichValueStructures struct {
106+
XMLName xml.Name `xml:"rvStructures"`
107+
Count int `xml:"count,attr,omitempty"`
108+
S []xlsxRichValueStructure `xml:"s"`
109+
ExtLst *xlsxInnerXML `xml:"extLst"`
110+
}
111+
112+
// xlsxRichValueStructure directly maps the s element. This element specifies
113+
// the list of rich value structures.
114+
type xlsxRichValueStructure struct {
115+
T string `xml:"t,attr"`
116+
K []xlsxRichValueKey `xml:"k"`
117+
}
118+
119+
// xlsxRichValueKey directly maps the k element. This element specifies the rich
120+
// value key.
121+
type xlsxRichValueKey struct {
122+
N string `xml:"n,attr"`
123+
T string `xml:"t,attr,omitempty"`
124+
}
125+
102126
// xlsxWebImagesSupportingRichData directly maps the webImagesSrd element. This
103127
// element specifies a list of sets of properties associated with web image rich
104128
// values.

0 commit comments

Comments
 (0)