Skip to content

Commit 00d6259

Browse files
oneweek20169902liying05
and
liying05
authored
This closes qax-os#664, support get embedded cell images (qax-os#1759)
Co-authored-by: liying05 <liying05@zhidemai.com>
1 parent dfaf418 commit 00d6259

8 files changed

+151
-19
lines changed

calc.go

+11
Original file line numberDiff line numberDiff line change
@@ -18640,3 +18640,14 @@ func (fn *formulaFuncs) DVAR(argsList *list.List) formulaArg {
1864018640
func (fn *formulaFuncs) DVARP(argsList *list.List) formulaArg {
1864118641
return fn.database("DVARP", argsList)
1864218642
}
18643+
18644+
// DISPIMG function calculates the Kingsoft WPS Office embedded image ID. The
18645+
// syntax of the function is:
18646+
//
18647+
// DISPIMG(picture_name,display_mode)
18648+
func (fn *formulaFuncs) DISPIMG(argsList *list.List) formulaArg {
18649+
if argsList.Len() != 2 {
18650+
return newErrorFormulaArg(formulaErrorVALUE, "DISPIMG requires 2 numeric arguments")
18651+
}
18652+
return argsList.Front().Value.(formulaArg)
18653+
}

calc_test.go

+4
Original file line numberDiff line numberDiff line change
@@ -2236,6 +2236,8 @@ func TestCalcCellValue(t *testing.T) {
22362236
// YIELDMAT
22372237
"=YIELDMAT(\"01/01/2017\",\"06/30/2018\",\"06/01/2014\",5.5%,101)": "0.0419422478838651",
22382238
"=YIELDMAT(\"01/01/2017\",\"06/30/2018\",\"06/01/2014\",5.5%,101,0)": "0.0419422478838651",
2239+
// DISPIMG
2240+
"=_xlfn.DISPIMG(\"ID_********************************\",1)": "ID_********************************",
22392241
}
22402242
for formula, expected := range mathCalc {
22412243
f := prepareCalcData(cellData)
@@ -4609,6 +4611,8 @@ func TestCalcCellValue(t *testing.T) {
46094611
"=YIELDMAT(\"01/01/2017\",\"06/30/2018\",\"06/01/2014\",-1,101,0)": {"#NUM!", "YIELDMAT requires rate >= 0"},
46104612
"=YIELDMAT(\"01/01/2017\",\"06/30/2018\",\"06/01/2014\",1,0,0)": {"#NUM!", "YIELDMAT requires pr > 0"},
46114613
"=YIELDMAT(\"01/01/2017\",\"06/30/2018\",\"06/01/2014\",5.5%,101,5)": {"#NUM!", "invalid basis"},
4614+
// DISPIMG
4615+
"=_xlfn.DISPIMG()": {"#VALUE!", "DISPIMG requires 2 numeric arguments"},
46124616
}
46134617
for formula, expected := range mathCalcError {
46144618
f := prepareCalcData(cellData)

chart.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -892,9 +892,9 @@ func (opts *Chart) parseTitle() {
892892
// The default width is 480, and height is 260.
893893
//
894894
// Set the bubble size in all data series for the bubble chart or 3D bubble
895-
// chart by 'BubbleSizes' property. The 'BubbleSizes' property is optional.
896-
// The default width is 100, and the value should be great than 0 and less or
897-
// equal than 300.
895+
// chart by 'BubbleSizes' property. The 'BubbleSizes' property is optional. The
896+
// default width is 100, and the value should be great than 0 and less or equal
897+
// than 300.
898898
//
899899
// Set the doughnut hole size in all data series for the doughnut chart by
900900
// 'HoleSize' property. The 'HoleSize' property is optional. The default width
@@ -932,7 +932,7 @@ func (opts *Chart) parseTitle() {
932932
// }
933933
// enable, disable := true, false
934934
// if err := f.AddChart("Sheet1", "E1", &excelize.Chart{
935-
// Type: "col",
935+
// Type: excelize.Col,
936936
// Series: []excelize.ChartSeries{
937937
// {
938938
// Name: "Sheet1!$A$2",
@@ -966,7 +966,7 @@ func (opts *Chart) parseTitle() {
966966
// ShowVal: true,
967967
// },
968968
// }, &excelize.Chart{
969-
// Type: "line",
969+
// Type: excelize.Line,
970970
// Series: []excelize.ChartSeries{
971971
// {
972972
// Name: "Sheet1!$A$4",

excelize.go

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ type File struct {
4343
Comments map[string]*xlsxComments
4444
ContentTypes *xlsxTypes
4545
DecodeVMLDrawing map[string]*decodeVmlDrawing
46+
DecodeCellImages *decodeCellImages
4647
Drawings sync.Map
4748
Path string
4849
Pkg sync.Map

picture.go

+64-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"bytes"
1616
"encoding/xml"
1717
"image"
18+
"io"
1819
"os"
1920
"path"
2021
"path/filepath"
@@ -467,14 +468,22 @@ func (f *File) GetPictures(sheet, cell string) ([]Picture, error) {
467468
}
468469
f.mu.Unlock()
469470
if ws.Drawing == nil {
470-
return nil, err
471+
return f.getCellImages(sheet, cell)
471472
}
472473
target := f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID)
473474
drawingXML := strings.TrimPrefix(strings.ReplaceAll(target, "..", "xl"), "/")
474475
drawingRelationships := strings.ReplaceAll(
475476
strings.ReplaceAll(target, "../drawings", "xl/drawings/_rels"), ".xml", ".xml.rels")
476477

477-
return f.getPicture(row, col, drawingXML, drawingRelationships)
478+
imgs, err := f.getCellImages(sheet, cell)
479+
if err != nil {
480+
return nil, err
481+
}
482+
pics, err := f.getPicture(row, col, drawingXML, drawingRelationships)
483+
if err != nil {
484+
return nil, err
485+
}
486+
return append(imgs, pics...), err
478487
}
479488

480489
// GetPictureCells returns all picture cell references in a worksheet by a
@@ -741,3 +750,56 @@ func (f *File) getPictureCells(drawingXML, drawingRelationships string) ([]strin
741750
}
742751
return cells, err
743752
}
753+
754+
// cellImagesReader provides a function to get the pointer to the structure
755+
// after deserialization of xl/cellimages.xml.
756+
func (f *File) cellImagesReader() (*decodeCellImages, error) {
757+
if f.DecodeCellImages == nil {
758+
f.DecodeCellImages = new(decodeCellImages)
759+
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathCellImages)))).
760+
Decode(f.DecodeCellImages); err != nil && err != io.EOF {
761+
return f.DecodeCellImages, err
762+
}
763+
}
764+
return f.DecodeCellImages, nil
765+
}
766+
767+
// getCellImages provides a function to get the Kingsoft WPS Office embedded
768+
// cell images by given worksheet name and cell reference.
769+
func (f *File) getCellImages(sheet, cell string) ([]Picture, error) {
770+
formula, err := f.GetCellFormula(sheet, cell)
771+
if err != nil {
772+
return nil, err
773+
}
774+
if !strings.HasPrefix(strings.TrimPrefix(strings.TrimPrefix(formula, "="), "_xlfn."), "DISPIMG") {
775+
return nil, err
776+
}
777+
imgID, err := f.CalcCellValue(sheet, cell)
778+
if err != nil {
779+
return nil, err
780+
}
781+
cellImages, err := f.cellImagesReader()
782+
if err != nil {
783+
return nil, err
784+
}
785+
rels, err := f.relsReader(defaultXMLPathCellImagesRels)
786+
if rels == nil {
787+
return nil, err
788+
}
789+
var pics []Picture
790+
for _, cellImg := range cellImages.CellImage {
791+
if cellImg.Pic.NvPicPr.CNvPr.Name == imgID {
792+
for _, r := range rels.Relationships {
793+
if r.ID == cellImg.Pic.BlipFill.Blip.Embed {
794+
pic := Picture{Extension: filepath.Ext(r.Target), Format: &GraphicOptions{}}
795+
if buffer, _ := f.Pkg.Load("xl/" + r.Target); buffer != nil {
796+
pic.File = buffer.([]byte)
797+
pic.Format.AltText = cellImg.Pic.NvPicPr.CNvPr.Descr
798+
pics = append(pics, pic)
799+
}
800+
}
801+
}
802+
}
803+
}
804+
return pics, err
805+
}

picture_test.go

+40
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ func TestGetPicture(t *testing.T) {
216216
cells, err := f.GetPictureCells("Sheet2")
217217
assert.NoError(t, err)
218218
assert.Equal(t, []string{"K16"}, cells)
219+
assert.NoError(t, f.Close())
219220

220221
// Test get picture from none drawing worksheet
221222
f = NewFile()
@@ -229,11 +230,41 @@ func TestGetPicture(t *testing.T) {
229230
path := "xl/drawings/drawing1.xml"
230231
f.Drawings.Delete(path)
231232
f.Pkg.Store(path, MacintoshCyrillicCharset)
233+
_, err = f.GetPictures("Sheet1", "F21")
234+
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
232235
_, err = f.getPicture(20, 5, path, "xl/drawings/_rels/drawing2.xml.rels")
233236
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
234237
f.Drawings.Delete(path)
235238
_, err = f.getPicture(20, 5, path, "xl/drawings/_rels/drawing2.xml.rels")
236239
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
240+
assert.NoError(t, f.Close())
241+
242+
// Test get embedded cell pictures
243+
f, err = OpenFile(filepath.Join("test", "TestGetPicture.xlsx"))
244+
assert.NoError(t, err)
245+
assert.NoError(t, f.SetCellFormula("Sheet1", "F21", "=_xlfn.DISPIMG(\"ID_********************************\",1)"))
246+
f.Pkg.Store(defaultXMLPathCellImages, []byte(`<etc:cellImages xmlns:etc="http://www.wps.cn/officeDocument/2017/etCustomData"><etc:cellImage><xdr:pic><xdr:nvPicPr><xdr:cNvPr id="1" name="ID_********************************" descr="CellImage1"/></xdr:nvPicPr><xdr:blipFill><a:blip r:embed="rId1"/></xdr:blipFill></xdr:pic></etc:cellImage></etc:cellImages>`))
247+
f.Pkg.Store(defaultXMLPathCellImagesRels, []byte(`<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/image1.jpeg"/></Relationships>`))
248+
pics, err = f.GetPictures("Sheet1", "F21")
249+
assert.NoError(t, err)
250+
assert.Len(t, pics, 2)
251+
assert.Equal(t, "CellImage1", pics[0].Format.AltText)
252+
253+
// Test get embedded cell pictures with invalid formula
254+
assert.NoError(t, f.SetCellFormula("Sheet1", "A1", "=_xlfn.DISPIMG()"))
255+
_, err = f.GetPictures("Sheet1", "A1")
256+
assert.EqualError(t, err, "DISPIMG requires 2 numeric arguments")
257+
258+
// Test get embedded cell pictures with unsupported charset
259+
f.Relationships.Delete(defaultXMLPathCellImagesRels)
260+
f.Pkg.Store(defaultXMLPathCellImagesRels, MacintoshCyrillicCharset)
261+
_, err = f.GetPictures("Sheet1", "F21")
262+
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
263+
f.Pkg.Store(defaultXMLPathCellImages, MacintoshCyrillicCharset)
264+
f.DecodeCellImages = nil
265+
_, err = f.GetPictures("Sheet1", "F21")
266+
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
267+
assert.NoError(t, f.Close())
237268
}
238269

239270
func TestAddDrawingPicture(t *testing.T) {
@@ -394,3 +425,12 @@ func TestExtractDecodeCellAnchor(t *testing.T) {
394425
cb := func(a *decodeCellAnchor, r *xlsxRelationship) {}
395426
f.extractDecodeCellAnchor(&xdrCellAnchor{GraphicFrame: string(MacintoshCyrillicCharset)}, "", cond, cb)
396427
}
428+
429+
func TestGetCellImages(t *testing.T) {
430+
f := NewFile()
431+
f.Sheet.Delete("xl/worksheets/sheet1.xml")
432+
f.Pkg.Store("xl/worksheets/sheet1.xml", MacintoshCyrillicCharset)
433+
_, err := f.getCellImages("Sheet1", "A1")
434+
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
435+
assert.NoError(t, f.Close())
436+
}

templates.go

+13-11
Original file line numberDiff line numberDiff line change
@@ -266,17 +266,19 @@ var supportedChartDataLabelsPosition = map[ChartType][]ChartDataLabelPositionTyp
266266
}
267267

268268
const (
269-
defaultTempFileSST = "sharedStrings"
270-
defaultXMLPathCalcChain = "xl/calcChain.xml"
271-
defaultXMLPathContentTypes = "[Content_Types].xml"
272-
defaultXMLPathDocPropsApp = "docProps/app.xml"
273-
defaultXMLPathDocPropsCore = "docProps/core.xml"
274-
defaultXMLPathSharedStrings = "xl/sharedStrings.xml"
275-
defaultXMLPathStyles = "xl/styles.xml"
276-
defaultXMLPathTheme = "xl/theme/theme1.xml"
277-
defaultXMLPathVolatileDeps = "xl/volatileDependencies.xml"
278-
defaultXMLPathWorkbook = "xl/workbook.xml"
279-
defaultXMLPathWorkbookRels = "xl/_rels/workbook.xml.rels"
269+
defaultTempFileSST = "sharedStrings"
270+
defaultXMLPathCalcChain = "xl/calcChain.xml"
271+
defaultXMLPathCellImages = "xl/cellimages.xml"
272+
defaultXMLPathCellImagesRels = "xl/_rels/cellimages.xml.rels"
273+
defaultXMLPathContentTypes = "[Content_Types].xml"
274+
defaultXMLPathDocPropsApp = "docProps/app.xml"
275+
defaultXMLPathDocPropsCore = "docProps/core.xml"
276+
defaultXMLPathSharedStrings = "xl/sharedStrings.xml"
277+
defaultXMLPathStyles = "xl/styles.xml"
278+
defaultXMLPathTheme = "xl/theme/theme1.xml"
279+
defaultXMLPathVolatileDeps = "xl/volatileDependencies.xml"
280+
defaultXMLPathWorkbook = "xl/workbook.xml"
281+
defaultXMLPathWorkbookRels = "xl/_rels/workbook.xml.rels"
280282
)
281283

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

xmlDecodeDrawing.go

+13-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ type decodeCNvSpPr struct {
8383
// changed after serialization and deserialization, two different structures
8484
// are defined. decodeWsDr just for deserialization.
8585
type decodeWsDr struct {
86-
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing wsDr,omitempty"`
86+
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing wsDr"`
8787
A string `xml:"xmlns a,attr"`
8888
Xdr string `xml:"xmlns xdr,attr"`
8989
R string `xml:"xmlns r,attr"`
@@ -242,3 +242,15 @@ type decodeClientData struct {
242242
FLocksWithSheet bool `xml:"fLocksWithSheet,attr"`
243243
FPrintsWithSheet bool `xml:"fPrintsWithSheet,attr"`
244244
}
245+
246+
// decodeCellImages directly maps the Kingsoft WPS Office embedded cell images.
247+
type decodeCellImages struct {
248+
XMLName xml.Name `xml:"http://www.wps.cn/officeDocument/2017/etCustomData cellImages"`
249+
CellImage []decodeCellImage `xml:"cellImage"`
250+
}
251+
252+
// decodeCellImage defines the structure used to deserialize the Kingsoft WPS
253+
// Office embedded cell images.
254+
type decodeCellImage struct {
255+
Pic decodePic `xml:"pic"`
256+
}

0 commit comments

Comments
 (0)