Add support for get the Microsoft 365 cell images (#1857)
- Update unit tests
This commit is contained in:
parent
703b73779c
commit
838232fd27
38
excelize.go
38
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
|
||||
}
|
||||
|
|
73
picture.go
73
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
|
||||
|
|
|
@ -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(`<metadata><valueMetadata count="1"><bk><rc t="1" v="0"/></bk></valueMetadata></metadata>`))
|
||||
f.Pkg.Store(defaultXMLRichDataRichValueRel, []byte(`<richValueRels><rel r:id="rId1"/></richValueRels>`))
|
||||
f.Pkg.Store(defaultXMLRichDataRichValueRelRels, []byte(fmt.Sprintf(`<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="%s" Target="../media/image1.png"/></Relationships>`, 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(`<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="%s" Target="../media/image1.png"/></Relationships>`, 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(`<metadata><valueMetadata count="1"><bk><rc t="1" v="0"/></bk></valueMetadata></metadata>`))
|
||||
f.Pkg.Store(defaultXMLRichDataRichValueRel, []byte(`<richValueRels/>`))
|
||||
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(`<metadata><valueMetadata/></metadata>`))
|
||||
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())
|
||||
}
|
||||
|
|
29
templates.go
29
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
|
||||
|
|
|
@ -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"`
|
||||
}
|
Loading…
Reference in New Issue