diff --git a/excelize.go b/excelize.go index 264101874709df7280c1333c317074fb72b3fbba..1bf0248b3fae05ceb02c1a3221c03561fcaabfa1 100644 --- a/excelize.go +++ b/excelize.go @@ -589,3 +589,41 @@ func (f *File) setContentTypePartProjectExtensions(contentType string) error { } return err } + +// metadataReader provides a function to get the pointer to the structure +// after deserialization of xl/metadata.xml. +func (f *File) metadataReader() (*xlsxMetadata, error) { + var mataData xlsxMetadata + if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLMetadata)))). + Decode(&mataData); err != nil && err != io.EOF { + return &mataData, err + } + return &mataData, nil +} + +// richValueRelReader provides a function to get the pointer to the structure +// after deserialization of xl/richData/richValueRel.xml. +func (f *File) richValueRelReader() (*xlsxRichValueRels, error) { + var richValueRels xlsxRichValueRels + if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLRichDataRichValueRel)))). + Decode(&richValueRels); err != nil && err != io.EOF { + return &richValueRels, err + } + return &richValueRels, nil +} + +// getRichDataRichValueRelRelationships provides a function to get drawing +// relationships from xl/richData/_rels/richValueRel.xml.rels by given +// relationship ID. +func (f *File) getRichDataRichValueRelRelationships(rID string) *xlsxRelationship { + if rels, _ := f.relsReader(defaultXMLRichDataRichValueRelRels); rels != nil { + rels.mu.Lock() + defer rels.mu.Unlock() + for _, v := range rels.Relationships { + if v.ID == rID { + return &v + } + } + } + return nil +} diff --git a/picture.go b/picture.go index 8b006f8e3ea83623af8b741fecc638737a3f8207..e6410dc9821ba87ecd0933f383dd0e8e58c64d6c 100644 --- a/picture.go +++ b/picture.go @@ -497,13 +497,13 @@ func (f *File) GetPictureCells(sheet string) ([]string, error) { } f.mu.Unlock() if ws.Drawing == nil { - return f.getEmbeddedImageCells(sheet) + return f.getImageCells(sheet) } 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") - embeddedImageCells, err := f.getEmbeddedImageCells(sheet) + embeddedImageCells, err := f.getImageCells(sheet) if err != nil { return nil, err } @@ -771,9 +771,9 @@ func (f *File) cellImagesReader() (*decodeCellImages, error) { return f.DecodeCellImages, nil } -// getEmbeddedImageCells returns all the Kingsoft WPS Office embedded image -// cells reference by given worksheet name. -func (f *File) getEmbeddedImageCells(sheet string) ([]string, error) { +// getImageCells returns all the Microsoft 365 cell images and the Kingsoft WPS +// Office embedded image cells reference by given worksheet name. +func (f *File) getImageCells(sheet string) ([]string, error) { var ( err error cells []string @@ -791,14 +791,73 @@ func (f *File) getEmbeddedImageCells(sheet string) ([]string, error) { } cells = append(cells, c.R) } + r, err := f.getImageCellRel(&c) + if err != nil { + return cells, err + } + if r != nil { + cells = append(cells, c.R) + } + } } return cells, err } -// getCellImages provides a function to get the Kingsoft WPS Office embedded -// cell images by given worksheet name and cell reference. +// getImageCellRel returns the Microsoft 365 cell image relationship. +func (f *File) getImageCellRel(c *xlsxC) (*xlsxRelationship, error) { + var r *xlsxRelationship + if c.Vm == nil || c.V != formulaErrorVALUE { + return r, nil + } + metaData, err := f.metadataReader() + if err != nil { + return r, err + } + vmd := metaData.ValueMetadata + if vmd == nil || int(*c.Vm) > len(vmd.Bk) || len(vmd.Bk[*c.Vm-1].Rc) == 0 { + return r, err + } + richValueRel, err := f.richValueRelReader() + if err != nil { + return r, err + } + if vmd.Bk[*c.Vm-1].Rc[0].V >= len(richValueRel.Rels) { + return r, err + } + rID := richValueRel.Rels[vmd.Bk[*c.Vm-1].Rc[0].V].ID + if r = f.getRichDataRichValueRelRelationships(rID); r != nil && r.Type != SourceRelationshipImage { + return nil, err + } + return r, err +} + +// getCellImages provides a function to get the Microsoft 365 cell images and +// the Kingsoft WPS Office embedded cell images by given worksheet name and cell +// reference. func (f *File) getCellImages(sheet, cell string) ([]Picture, error) { + pics, err := f.getDispImages(sheet, cell) + if err != nil { + return pics, err + } + _, err = f.getCellStringFunc(sheet, cell, func(x *xlsxWorksheet, c *xlsxC) (string, bool, error) { + r, err := f.getImageCellRel(c) + if err != nil || r == nil { + return "", true, err + } + pic := Picture{Extension: filepath.Ext(r.Target), Format: &GraphicOptions{}} + if buffer, _ := f.Pkg.Load(strings.TrimPrefix(strings.ReplaceAll(r.Target, "..", "xl"), "/")); buffer != nil { + pic.File = buffer.([]byte) + pics = append(pics, pic) + } + return "", true, nil + }) + return pics, err +} + +// getDispImages provides a function to get the Kingsoft WPS Office embedded +// cell images by given worksheet name and cell reference. +func (f *File) getDispImages(sheet, cell string) ([]Picture, error) { formula, err := f.GetCellFormula(sheet, cell) if err != nil { return nil, err diff --git a/picture_test.go b/picture_test.go index 376f3997c69ed67b671b257557f738fd3704dece..dfd5f623daa0b16d1cae16be30f1be125fd1be87 100644 --- a/picture_test.go +++ b/picture_test.go @@ -448,13 +448,67 @@ func TestGetCellImages(t *testing.T) { _, err := f.getCellImages("Sheet1", "A1") assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") assert.NoError(t, f.Close()) + + // Test get the Microsoft 365 cell images + f = NewFile() + assert.NoError(t, f.AddPicture("Sheet1", "A1", filepath.Join("test", "images", "excel.png"), nil)) + f.Pkg.Store(defaultXMLMetadata, []byte(``)) + f.Pkg.Store(defaultXMLRichDataRichValueRel, []byte(``)) + f.Pkg.Store(defaultXMLRichDataRichValueRelRels, []byte(fmt.Sprintf(``, SourceRelationshipImage))) + f.Sheet.Store("xl/worksheets/sheet1.xml", &xlsxWorksheet{ + SheetData: xlsxSheetData{Row: []xlsxRow{ + {R: 1, C: []xlsxC{{R: "A1", T: "e", V: formulaErrorVALUE, Vm: uintPtr(1)}}}, + }}, + }) + pics, err := f.GetPictures("Sheet1", "A1") + assert.NoError(t, err) + assert.Equal(t, 1, len(pics)) + cells, err := f.GetPictureCells("Sheet1") + assert.NoError(t, err) + assert.Equal(t, []string{"A1"}, cells) + + // Test get the Microsoft 365 cell images without image relationships parts + f.Relationships.Delete(defaultXMLRichDataRichValueRelRels) + f.Pkg.Store(defaultXMLRichDataRichValueRelRels, []byte(fmt.Sprintf(``, SourceRelationshipHyperLink))) + pics, err = f.GetPictures("Sheet1", "A1") + assert.NoError(t, err) + assert.Empty(t, pics) + // Test get the Microsoft 365 cell images with unsupported charset rich data rich value relationships + f.Relationships.Delete(defaultXMLRichDataRichValueRelRels) + f.Pkg.Store(defaultXMLRichDataRichValueRelRels, MacintoshCyrillicCharset) + pics, err = f.GetPictures("Sheet1", "A1") + assert.NoError(t, err) + assert.Empty(t, pics) + // Test get the Microsoft 365 cell images with unsupported charset rich data rich value + f.Pkg.Store(defaultXMLRichDataRichValueRel, MacintoshCyrillicCharset) + _, err = f.GetPictures("Sheet1", "A1") + assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") + // Test get the Microsoft 365 image cells without block of metadata records + cells, err = f.GetPictureCells("Sheet1") + assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") + assert.Empty(t, cells) + // Test get the Microsoft 365 cell images with rich data rich value relationships + f.Pkg.Store(defaultXMLMetadata, []byte(``)) + f.Pkg.Store(defaultXMLRichDataRichValueRel, []byte(``)) + pics, err = f.GetPictures("Sheet1", "A1") + assert.NoError(t, err) + assert.Empty(t, pics) + // Test get the Microsoft 365 cell images with unsupported charset meta data + f.Pkg.Store(defaultXMLMetadata, MacintoshCyrillicCharset) + _, err = f.GetPictures("Sheet1", "A1") + assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") + // Test get the Microsoft 365 cell images without block of metadata records + f.Pkg.Store(defaultXMLMetadata, []byte(``)) + pics, err = f.GetPictures("Sheet1", "A1") + assert.NoError(t, err) + assert.Empty(t, pics) } -func TestGetEmbeddedImageCells(t *testing.T) { +func TestGetImageCells(t *testing.T) { f := NewFile() f.Sheet.Delete("xl/worksheets/sheet1.xml") f.Pkg.Store("xl/worksheets/sheet1.xml", MacintoshCyrillicCharset) - _, err := f.getEmbeddedImageCells("Sheet1") + _, err := f.getImageCells("Sheet1") 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 0f21be54bcaf76b7ade9263c03dee3fe76dc6050..e097e818057578c0fe3cf3024527e5be344b59ac 100644 --- a/templates.go +++ b/templates.go @@ -266,19 +266,22 @@ var supportedChartDataLabelsPosition = map[ChartType][]ChartDataLabelPositionTyp } const ( - 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" + defaultTempFileSST = "sharedStrings" + defaultXMLMetadata = "xl/metadata.xml" + 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" + defaultXMLRichDataRichValueRel = "xl/richData/richValueRel.xml" + defaultXMLRichDataRichValueRelRels = "xl/richData/_rels/richValueRel.xml.rels" ) // IndexedColorMapping is the table of default mappings from indexed color value diff --git a/xmlMetaData.go b/xmlMetaData.go new file mode 100644 index 0000000000000000000000000000000000000000..e8cf7dbb8c44ea8e4757624d4e8572eb8ec1ba4d --- /dev/null +++ b/xmlMetaData.go @@ -0,0 +1,83 @@ +// Copyright 2016 - 2024 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to and +// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and +// writing spreadsheet documents generated by Microsoft Excelâ„¢ 2007 and later. +// Supports complex components by high compatibility, and provided streaming +// API for generating or reading data from a worksheet with huge amounts of +// data. This library needs Go version 1.18 or later. + +package excelize + +import "encoding/xml" + +// xlsxMetadata directly maps the metadata element. A cell in a spreadsheet +// application can have metadata associated with it. Metadata is just a set of +// additional properties about the particular cell, and this metadata is stored +// in the metadata xml part. There are two types of metadata: cell metadata and +// value metadata. Cell metadata contains information about the cell itself, +// and this metadata can be carried along with the cell as it moves +// (insert, shift, copy/paste, merge, unmerge, etc). Value metadata is +// information about the value of a particular cell. Value metadata properties +// can be propagated along with the value as it is referenced in formulas. +type xlsxMetadata struct { + XMLName xml.Name `xml:"metadata"` + MetadataTypes *xlsxInnerXML `xml:"metadataTypes"` + MetadataStrings *xlsxInnerXML `xml:"metadataStrings"` + MdxMetadata *xlsxInnerXML `xml:"mdxMetadata"` + FutureMetadata []xlsxFutureMetadata `xml:"futureMetadata"` + CellMetadata *xlsxMetadataBlocks `xml:"cellMetadata"` + ValueMetadata *xlsxMetadataBlocks `xml:"valueMetadata"` + ExtLst *xlsxInnerXML `xml:"extLst"` +} + +// xlsxFutureMetadata directly maps the futureMetadata element. This element +// represents future metadata information. +type xlsxFutureMetadata struct { + Bk []xlsxFutureMetadataBlock `xml:"bk"` + ExtLst *xlsxInnerXML `xml:"extLst"` +} + +// xlsxFutureMetadataBlock directly maps the kb element. This element represents +// a block of future metadata information. This is a location for storing +// feature extension information. +type xlsxFutureMetadataBlock struct { + ExtLst *xlsxInnerXML `xml:"extLst"` +} + +// xlsxMetadataBlocks directly maps the metadata element. This element +// represents cell metadata information. Cell metadata is information metadata +// about a specific cell, and it stays tied to that cell position. +type xlsxMetadataBlocks struct { + Count int `xml:"count,attr,omitempty"` + Bk []xlsxMetadataBlock `xml:"bk"` +} + +// xlsxMetadataBlock directly maps the bk element. This element represents a +// block of metadata records. +type xlsxMetadataBlock struct { + Rc []xlsxMetadataRecord `xml:"rc"` +} + +// xlsxMetadataRecord directly maps the rc element. This element represents a +// reference to a specific metadata record. +type xlsxMetadataRecord struct { + T int `xml:"t,attr"` + V int `xml:"v,attr"` +} + +// xlsxRichValueRels directly maps the richValueRels element. This element that +// specifies a list of rich value relationships. +type xlsxRichValueRels struct { + XMLName xml.Name `xml:"richValueRels"` + Rels []xlsxRichValueRelRelationship `xml:"rel"` + ExtLst *xlsxInnerXML `xml:"extLst"` +} + +// xlsxRichValueRelRelationship directly maps the rel element. This element +// specifies a relationship for a rich value property. +type xlsxRichValueRelRelationship struct { + ID string `xml:"id,attr"` +}