diff --git a/calc.go b/calc.go
index a5dbd72..8fd207a 100644
--- a/calc.go
+++ b/calc.go
@@ -18640,3 +18640,14 @@ func (fn *formulaFuncs) DVAR(argsList *list.List) formulaArg {
func (fn *formulaFuncs) DVARP(argsList *list.List) formulaArg {
return fn.database("DVARP", argsList)
}
+
+// DISPIMG function calculates the Kingsoft WPS Office embedded image ID. The
+// syntax of the function is:
+//
+// DISPIMG(picture_name,display_mode)
+func (fn *formulaFuncs) DISPIMG(argsList *list.List) formulaArg {
+ if argsList.Len() != 2 {
+ return newErrorFormulaArg(formulaErrorVALUE, "DISPIMG requires 2 numeric arguments")
+ }
+ return argsList.Front().Value.(formulaArg)
+}
diff --git a/calc_test.go b/calc_test.go
index 71f3396..a3a6a83 100644
--- a/calc_test.go
+++ b/calc_test.go
@@ -2236,6 +2236,8 @@ func TestCalcCellValue(t *testing.T) {
// YIELDMAT
"=YIELDMAT(\"01/01/2017\",\"06/30/2018\",\"06/01/2014\",5.5%,101)": "0.0419422478838651",
"=YIELDMAT(\"01/01/2017\",\"06/30/2018\",\"06/01/2014\",5.5%,101,0)": "0.0419422478838651",
+ // DISPIMG
+ "=_xlfn.DISPIMG(\"ID_********************************\",1)": "ID_********************************",
}
for formula, expected := range mathCalc {
f := prepareCalcData(cellData)
@@ -4609,6 +4611,8 @@ func TestCalcCellValue(t *testing.T) {
"=YIELDMAT(\"01/01/2017\",\"06/30/2018\",\"06/01/2014\",-1,101,0)": {"#NUM!", "YIELDMAT requires rate >= 0"},
"=YIELDMAT(\"01/01/2017\",\"06/30/2018\",\"06/01/2014\",1,0,0)": {"#NUM!", "YIELDMAT requires pr > 0"},
"=YIELDMAT(\"01/01/2017\",\"06/30/2018\",\"06/01/2014\",5.5%,101,5)": {"#NUM!", "invalid basis"},
+ // DISPIMG
+ "=_xlfn.DISPIMG()": {"#VALUE!", "DISPIMG requires 2 numeric arguments"},
}
for formula, expected := range mathCalcError {
f := prepareCalcData(cellData)
diff --git a/chart.go b/chart.go
index ffc0456..8296826 100644
--- a/chart.go
+++ b/chart.go
@@ -892,9 +892,9 @@ func (opts *Chart) parseTitle() {
// The default width is 480, and height is 260.
//
// Set the bubble size in all data series for the bubble chart or 3D bubble
-// chart by 'BubbleSizes' property. The 'BubbleSizes' property is optional.
-// The default width is 100, and the value should be great than 0 and less or
-// equal than 300.
+// chart by 'BubbleSizes' property. The 'BubbleSizes' property is optional. The
+// default width is 100, and the value should be great than 0 and less or equal
+// than 300.
//
// Set the doughnut hole size in all data series for the doughnut chart by
// 'HoleSize' property. The 'HoleSize' property is optional. The default width
@@ -932,7 +932,7 @@ func (opts *Chart) parseTitle() {
// }
// enable, disable := true, false
// if err := f.AddChart("Sheet1", "E1", &excelize.Chart{
-// Type: "col",
+// Type: excelize.Col,
// Series: []excelize.ChartSeries{
// {
// Name: "Sheet1!$A$2",
@@ -966,7 +966,7 @@ func (opts *Chart) parseTitle() {
// ShowVal: true,
// },
// }, &excelize.Chart{
-// Type: "line",
+// Type: excelize.Line,
// Series: []excelize.ChartSeries{
// {
// Name: "Sheet1!$A$4",
diff --git a/excelize.go b/excelize.go
index 0b85760..b7dd508 100644
--- a/excelize.go
+++ b/excelize.go
@@ -43,6 +43,7 @@ type File struct {
Comments map[string]*xlsxComments
ContentTypes *xlsxTypes
DecodeVMLDrawing map[string]*decodeVmlDrawing
+ DecodeCellImages *decodeCellImages
Drawings sync.Map
Path string
Pkg sync.Map
diff --git a/picture.go b/picture.go
index 9411f07..1c0c8c7 100644
--- a/picture.go
+++ b/picture.go
@@ -15,6 +15,7 @@ import (
"bytes"
"encoding/xml"
"image"
+ "io"
"os"
"path"
"path/filepath"
@@ -467,14 +468,22 @@ func (f *File) GetPictures(sheet, cell string) ([]Picture, error) {
}
f.mu.Unlock()
if ws.Drawing == nil {
- return nil, err
+ return f.getCellImages(sheet, cell)
}
target := f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID)
drawingXML := strings.TrimPrefix(strings.ReplaceAll(target, "..", "xl"), "/")
drawingRelationships := strings.ReplaceAll(
strings.ReplaceAll(target, "../drawings", "xl/drawings/_rels"), ".xml", ".xml.rels")
- return f.getPicture(row, col, drawingXML, drawingRelationships)
+ imgs, err := f.getCellImages(sheet, cell)
+ if err != nil {
+ return nil, err
+ }
+ pics, err := f.getPicture(row, col, drawingXML, drawingRelationships)
+ if err != nil {
+ return nil, err
+ }
+ return append(imgs, pics...), err
}
// GetPictureCells returns all picture cell references in a worksheet by a
@@ -741,3 +750,56 @@ func (f *File) getPictureCells(drawingXML, drawingRelationships string) ([]strin
}
return cells, err
}
+
+// cellImagesReader provides a function to get the pointer to the structure
+// after deserialization of xl/cellimages.xml.
+func (f *File) cellImagesReader() (*decodeCellImages, error) {
+ if f.DecodeCellImages == nil {
+ f.DecodeCellImages = new(decodeCellImages)
+ if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathCellImages)))).
+ Decode(f.DecodeCellImages); err != nil && err != io.EOF {
+ return f.DecodeCellImages, err
+ }
+ }
+ return f.DecodeCellImages, nil
+}
+
+// getCellImages provides a function to get the Kingsoft WPS Office embedded
+// cell images by given worksheet name and cell reference.
+func (f *File) getCellImages(sheet, cell string) ([]Picture, error) {
+ formula, err := f.GetCellFormula(sheet, cell)
+ if err != nil {
+ return nil, err
+ }
+ if !strings.HasPrefix(strings.TrimPrefix(strings.TrimPrefix(formula, "="), "_xlfn."), "DISPIMG") {
+ return nil, err
+ }
+ imgID, err := f.CalcCellValue(sheet, cell)
+ if err != nil {
+ return nil, err
+ }
+ cellImages, err := f.cellImagesReader()
+ if err != nil {
+ return nil, err
+ }
+ rels, err := f.relsReader(defaultXMLPathCellImagesRels)
+ if rels == nil {
+ return nil, err
+ }
+ var pics []Picture
+ for _, cellImg := range cellImages.CellImage {
+ if cellImg.Pic.NvPicPr.CNvPr.Name == imgID {
+ for _, r := range rels.Relationships {
+ if r.ID == cellImg.Pic.BlipFill.Blip.Embed {
+ pic := Picture{Extension: filepath.Ext(r.Target), Format: &GraphicOptions{}}
+ if buffer, _ := f.Pkg.Load("xl/" + r.Target); buffer != nil {
+ pic.File = buffer.([]byte)
+ pic.Format.AltText = cellImg.Pic.NvPicPr.CNvPr.Descr
+ pics = append(pics, pic)
+ }
+ }
+ }
+ }
+ }
+ return pics, err
+}
diff --git a/picture_test.go b/picture_test.go
index b98941f..3573f45 100644
--- a/picture_test.go
+++ b/picture_test.go
@@ -216,6 +216,7 @@ func TestGetPicture(t *testing.T) {
cells, err := f.GetPictureCells("Sheet2")
assert.NoError(t, err)
assert.Equal(t, []string{"K16"}, cells)
+ assert.NoError(t, f.Close())
// Test get picture from none drawing worksheet
f = NewFile()
@@ -229,11 +230,41 @@ func TestGetPicture(t *testing.T) {
path := "xl/drawings/drawing1.xml"
f.Drawings.Delete(path)
f.Pkg.Store(path, MacintoshCyrillicCharset)
+ _, err = f.GetPictures("Sheet1", "F21")
+ assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
_, err = f.getPicture(20, 5, path, "xl/drawings/_rels/drawing2.xml.rels")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
f.Drawings.Delete(path)
_, err = f.getPicture(20, 5, path, "xl/drawings/_rels/drawing2.xml.rels")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
+ assert.NoError(t, f.Close())
+
+ // Test get embedded cell pictures
+ f, err = OpenFile(filepath.Join("test", "TestGetPicture.xlsx"))
+ assert.NoError(t, err)
+ assert.NoError(t, f.SetCellFormula("Sheet1", "F21", "=_xlfn.DISPIMG(\"ID_********************************\",1)"))
+ f.Pkg.Store(defaultXMLPathCellImages, []byte(``))
+ f.Pkg.Store(defaultXMLPathCellImagesRels, []byte(``))
+ pics, err = f.GetPictures("Sheet1", "F21")
+ assert.NoError(t, err)
+ assert.Len(t, pics, 2)
+ assert.Equal(t, "CellImage1", pics[0].Format.AltText)
+
+ // Test get embedded cell pictures with invalid formula
+ assert.NoError(t, f.SetCellFormula("Sheet1", "A1", "=_xlfn.DISPIMG()"))
+ _, err = f.GetPictures("Sheet1", "A1")
+ assert.EqualError(t, err, "DISPIMG requires 2 numeric arguments")
+
+ // Test get embedded cell pictures with unsupported charset
+ f.Relationships.Delete(defaultXMLPathCellImagesRels)
+ f.Pkg.Store(defaultXMLPathCellImagesRels, MacintoshCyrillicCharset)
+ _, err = f.GetPictures("Sheet1", "F21")
+ assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
+ f.Pkg.Store(defaultXMLPathCellImages, MacintoshCyrillicCharset)
+ f.DecodeCellImages = nil
+ _, err = f.GetPictures("Sheet1", "F21")
+ assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
+ assert.NoError(t, f.Close())
}
func TestAddDrawingPicture(t *testing.T) {
@@ -394,3 +425,12 @@ func TestExtractDecodeCellAnchor(t *testing.T) {
cb := func(a *decodeCellAnchor, r *xlsxRelationship) {}
f.extractDecodeCellAnchor(&xdrCellAnchor{GraphicFrame: string(MacintoshCyrillicCharset)}, "", cond, cb)
}
+
+func TestGetCellImages(t *testing.T) {
+ f := NewFile()
+ f.Sheet.Delete("xl/worksheets/sheet1.xml")
+ f.Pkg.Store("xl/worksheets/sheet1.xml", MacintoshCyrillicCharset)
+ _, err := f.getCellImages("Sheet1", "A1")
+ assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
+ assert.NoError(t, f.Close())
+}
diff --git a/templates.go b/templates.go
index 3861f6b..43d6df4 100644
--- a/templates.go
+++ b/templates.go
@@ -266,17 +266,19 @@ var supportedChartDataLabelsPosition = map[ChartType][]ChartDataLabelPositionTyp
}
const (
- defaultTempFileSST = "sharedStrings"
- defaultXMLPathCalcChain = "xl/calcChain.xml"
- defaultXMLPathContentTypes = "[Content_Types].xml"
- defaultXMLPathDocPropsApp = "docProps/app.xml"
- defaultXMLPathDocPropsCore = "docProps/core.xml"
- defaultXMLPathSharedStrings = "xl/sharedStrings.xml"
- defaultXMLPathStyles = "xl/styles.xml"
- defaultXMLPathTheme = "xl/theme/theme1.xml"
- defaultXMLPathVolatileDeps = "xl/volatileDependencies.xml"
- defaultXMLPathWorkbook = "xl/workbook.xml"
- defaultXMLPathWorkbookRels = "xl/_rels/workbook.xml.rels"
+ defaultTempFileSST = "sharedStrings"
+ defaultXMLPathCalcChain = "xl/calcChain.xml"
+ defaultXMLPathCellImages = "xl/cellimages.xml"
+ defaultXMLPathCellImagesRels = "xl/_rels/cellimages.xml.rels"
+ defaultXMLPathContentTypes = "[Content_Types].xml"
+ defaultXMLPathDocPropsApp = "docProps/app.xml"
+ defaultXMLPathDocPropsCore = "docProps/core.xml"
+ defaultXMLPathSharedStrings = "xl/sharedStrings.xml"
+ defaultXMLPathStyles = "xl/styles.xml"
+ defaultXMLPathTheme = "xl/theme/theme1.xml"
+ defaultXMLPathVolatileDeps = "xl/volatileDependencies.xml"
+ defaultXMLPathWorkbook = "xl/workbook.xml"
+ defaultXMLPathWorkbookRels = "xl/_rels/workbook.xml.rels"
)
// IndexedColorMapping is the table of default mappings from indexed color value
diff --git a/xmlDecodeDrawing.go b/xmlDecodeDrawing.go
index fb4ea07..1473817 100644
--- a/xmlDecodeDrawing.go
+++ b/xmlDecodeDrawing.go
@@ -83,7 +83,7 @@ type decodeCNvSpPr struct {
// changed after serialization and deserialization, two different structures
// are defined. decodeWsDr just for deserialization.
type decodeWsDr struct {
- XMLName xml.Name `xml:"http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing wsDr,omitempty"`
+ XMLName xml.Name `xml:"http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing wsDr"`
A string `xml:"xmlns a,attr"`
Xdr string `xml:"xmlns xdr,attr"`
R string `xml:"xmlns r,attr"`
@@ -242,3 +242,15 @@ type decodeClientData struct {
FLocksWithSheet bool `xml:"fLocksWithSheet,attr"`
FPrintsWithSheet bool `xml:"fPrintsWithSheet,attr"`
}
+
+// decodeCellImages directly maps the Kingsoft WPS Office embedded cell images.
+type decodeCellImages struct {
+ XMLName xml.Name `xml:"http://www.wps.cn/officeDocument/2017/etCustomData cellImages"`
+ CellImage []decodeCellImage `xml:"cellImage"`
+}
+
+// decodeCellImage defines the structure used to deserialize the Kingsoft WPS
+// Office embedded cell images.
+type decodeCellImage struct {
+ Pic decodePic `xml:"pic"`
+}