diff --git a/comment_test.go b/comment_test.go index 952763dfd7..01f1e42e3e 100644 --- a/comment_test.go +++ b/comment_test.go @@ -12,6 +12,7 @@ package excelize import ( + "encoding/xml" "path/filepath" "strings" "testing" @@ -38,7 +39,7 @@ func TestAddComments(t *testing.T) { } f.Comments["xl/comments2.xml"] = nil - f.Pkg.Store("xl/comments2.xml", []byte(`Excelize: Excelize: `)) + f.Pkg.Store("xl/comments2.xml", []byte(xml.Header+`Excelize: Excelize: `)) comments := f.GetComments() assert.EqualValues(t, 2, len(comments["Sheet1"])) assert.EqualValues(t, 1, len(comments["Sheet2"])) diff --git a/drawing.go b/drawing.go index 0bc79005f7..5582bb4ff1 100644 --- a/drawing.go +++ b/drawing.go @@ -1160,6 +1160,12 @@ func (f *File) drawingParser(path string) (*xlsxWsDr, int) { log.Printf("xml decode error: %s", err) } content.R = decodeWsDr.R + for _, v := range decodeWsDr.AlternateContent { + content.AlternateContent = append(content.AlternateContent, &xlsxAlternateContent{ + Content: v.Content, + XMLNSMC: SourceRelationshipCompatibility.Value, + }) + } for _, v := range decodeWsDr.OneCellAnchor { content.OneCellAnchor = append(content.OneCellAnchor, &xdrCellAnchor{ EditAs: v.EditAs, diff --git a/drawing_test.go b/drawing_test.go index d33977fb89..e37b771fdd 100644 --- a/drawing_test.go +++ b/drawing_test.go @@ -12,6 +12,7 @@ package excelize import ( + "encoding/xml" "sync" "testing" ) @@ -22,9 +23,13 @@ func TestDrawingParser(t *testing.T) { Pkg: sync.Map{}, } f.Pkg.Store("charset", MacintoshCyrillicCharset) - f.Pkg.Store("wsDr", []byte(``)) + f.Pkg.Store("wsDr", []byte(xml.Header+``)) // Test with one cell anchor f.drawingParser("wsDr") // Test with unsupported charset f.drawingParser("charset") + // Test with alternate content + f.Drawings = sync.Map{} + f.Pkg.Store("wsDr", []byte(xml.Header+``)) + f.drawingParser("wsDr") } diff --git a/rows_test.go b/rows_test.go index 1286a3773d..208b2de841 100644 --- a/rows_test.go +++ b/rows_test.go @@ -2,6 +2,7 @@ package excelize import ( "bytes" + "encoding/xml" "fmt" "path/filepath" "testing" @@ -901,12 +902,12 @@ func TestErrSheetNotExistError(t *testing.T) { func TestCheckRow(t *testing.T) { f := NewFile() - f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`12345`)) + f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(xml.Header+`12345`)) _, err := f.GetRows("Sheet1") assert.NoError(t, err) assert.NoError(t, f.SetCellValue("Sheet1", "A1", false)) f = NewFile() - f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`12345`)) + f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(xml.Header+`12345`)) f.Sheet.Delete("xl/worksheets/sheet1.xml") delete(f.checked, "xl/worksheets/sheet1.xml") assert.EqualError(t, f.SetCellValue("Sheet1", "A1", false), newCellNameToCoordinatesError("-", newInvalidCellNameError("-")).Error()) diff --git a/sheet.go b/sheet.go index b440fb945f..78fcaf2130 100644 --- a/sheet.go +++ b/sheet.go @@ -158,6 +158,13 @@ func (f *File) workSheetWriter() { if sheet.SheetPr != nil || sheet.Drawing != nil || sheet.Hyperlinks != nil || sheet.Picture != nil || sheet.TableParts != nil { f.addNameSpaces(p.(string), SourceRelationship) } + if sheet.DecodeAlternateContent != nil { + sheet.AlternateContent = &xlsxAlternateContent{ + Content: sheet.DecodeAlternateContent.Content, + XMLNSMC: SourceRelationshipCompatibility.Value, + } + } + sheet.DecodeAlternateContent = nil // reusing buffer _ = encoder.Encode(sheet) f.saveFileList(p.(string), replaceRelationshipsBytes(f.replaceNameSpaceBytes(p.(string), buffer.Bytes()))) diff --git a/sheet_test.go b/sheet_test.go index 7df9018b1c..429f617247 100644 --- a/sheet_test.go +++ b/sheet_test.go @@ -1,6 +1,7 @@ package excelize import ( + "encoding/xml" "fmt" "path/filepath" "strconv" @@ -404,6 +405,20 @@ func TestSetSheetName(t *testing.T) { assert.Equal(t, "Sheet1", f.GetSheetName(0)) } +func TestWorksheetWriter(t *testing.T) { + f := NewFile() + // Test set cell value with alternate content + f.Sheet.Delete("xl/worksheets/sheet1.xml") + worksheet := xml.Header + `%d` + f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(worksheet, 1))) + f.checked = nil + assert.NoError(t, f.SetCellValue("Sheet1", "A1", 2)) + f.workSheetWriter() + value, ok := f.Pkg.Load("xl/worksheets/sheet1.xml") + assert.True(t, ok) + assert.Equal(t, fmt.Sprintf(worksheet, 2), string(value.([]byte))) +} + func TestGetWorkbookPath(t *testing.T) { f := NewFile() f.Pkg.Delete("_rels/.rels") @@ -413,7 +428,7 @@ func TestGetWorkbookPath(t *testing.T) { func TestGetWorkbookRelsPath(t *testing.T) { f := NewFile() f.Pkg.Delete("xl/_rels/.rels") - f.Pkg.Store("_rels/.rels", []byte(``)) + f.Pkg.Store("_rels/.rels", []byte(xml.Header+``)) assert.Equal(t, "_rels/workbook.xml.rels", f.getWorkbookRelsPath()) } diff --git a/workbook.go b/workbook.go index 3d9fa480b2..c65397b6fc 100644 --- a/workbook.go +++ b/workbook.go @@ -101,6 +101,13 @@ func (f *File) workbookReader() *xlsxWorkbook { // structure. func (f *File) workBookWriter() { if f.WorkBook != nil { + if f.WorkBook.DecodeAlternateContent != nil { + f.WorkBook.AlternateContent = &xlsxAlternateContent{ + Content: f.WorkBook.DecodeAlternateContent.Content, + XMLNSMC: SourceRelationshipCompatibility.Value, + } + } + f.WorkBook.DecodeAlternateContent = nil output, _ := xml.Marshal(f.WorkBook) f.saveFileList(f.getWorkbookPath(), replaceRelationshipsBytes(f.replaceNameSpaceBytes(f.getWorkbookPath(), output))) } diff --git a/xmlDecodeDrawing.go b/xmlDecodeDrawing.go index 9091440023..fb920be1d8 100644 --- a/xmlDecodeDrawing.go +++ b/xmlDecodeDrawing.go @@ -63,12 +63,13 @@ type decodeCNvSpPr struct { // changed after serialization and deserialization, two different structures // are defined. decodeWsDr just for deserialization. type decodeWsDr struct { - A string `xml:"xmlns a,attr"` - Xdr string `xml:"xmlns xdr,attr"` - R string `xml:"xmlns r,attr"` - OneCellAnchor []*decodeCellAnchor `xml:"oneCellAnchor,omitempty"` - TwoCellAnchor []*decodeCellAnchor `xml:"twoCellAnchor,omitempty"` - 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,omitempty"` + A string `xml:"xmlns a,attr"` + Xdr string `xml:"xmlns xdr,attr"` + R string `xml:"xmlns r,attr"` + AlternateContent []*xlsxInnerXML `xml:"http://schemas.openxmlformats.org/markup-compatibility/2006 AlternateContent"` + OneCellAnchor []*decodeCellAnchor `xml:"oneCellAnchor,omitempty"` + TwoCellAnchor []*decodeCellAnchor `xml:"twoCellAnchor,omitempty"` } // decodeTwoCellAnchor directly maps the oneCellAnchor (One Cell Anchor Shape diff --git a/xmlDrawing.go b/xmlDrawing.go index c96034ce5c..4bf43ec89b 100644 --- a/xmlDrawing.go +++ b/xmlDrawing.go @@ -320,13 +320,14 @@ type xlsxPoint2D struct { // wsDr. type xlsxWsDr struct { sync.Mutex - XMLName xml.Name `xml:"xdr:wsDr"` - AbsoluteAnchor []*xdrCellAnchor `xml:"xdr:absoluteAnchor"` - OneCellAnchor []*xdrCellAnchor `xml:"xdr:oneCellAnchor"` - TwoCellAnchor []*xdrCellAnchor `xml:"xdr:twoCellAnchor"` - A string `xml:"xmlns:a,attr,omitempty"` - Xdr string `xml:"xmlns:xdr,attr,omitempty"` - R string `xml:"xmlns:r,attr,omitempty"` + XMLName xml.Name `xml:"xdr:wsDr"` + A string `xml:"xmlns:a,attr,omitempty"` + Xdr string `xml:"xmlns:xdr,attr,omitempty"` + R string `xml:"xmlns:r,attr,omitempty"` + AlternateContent []*xlsxAlternateContent `xml:"mc:AlternateContent"` + AbsoluteAnchor []*xdrCellAnchor `xml:"xdr:absoluteAnchor"` + OneCellAnchor []*xdrCellAnchor `xml:"xdr:oneCellAnchor"` + TwoCellAnchor []*xdrCellAnchor `xml:"xdr:twoCellAnchor"` } // xlsxGraphicFrame (Graphic Frame) directly maps the xdr:graphicFrame element. diff --git a/xmlWorkbook.go b/xmlWorkbook.go index 2bb417c36d..e344dbff99 100644 --- a/xmlWorkbook.go +++ b/xmlWorkbook.go @@ -35,27 +35,29 @@ type xlsxRelationship struct { // content of the workbook. The workbook's child elements each have their own // subclause references. type xlsxWorkbook struct { - XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main workbook"` - Conformance string `xml:"conformance,attr,omitempty"` - FileVersion *xlsxFileVersion `xml:"fileVersion"` - FileSharing *xlsxExtLst `xml:"fileSharing"` - WorkbookPr *xlsxWorkbookPr `xml:"workbookPr"` - WorkbookProtection *xlsxWorkbookProtection `xml:"workbookProtection"` - BookViews *xlsxBookViews `xml:"bookViews"` - Sheets xlsxSheets `xml:"sheets"` - FunctionGroups *xlsxExtLst `xml:"functionGroups"` - ExternalReferences *xlsxExternalReferences `xml:"externalReferences"` - DefinedNames *xlsxDefinedNames `xml:"definedNames"` - CalcPr *xlsxCalcPr `xml:"calcPr"` - OleSize *xlsxExtLst `xml:"oleSize"` - CustomWorkbookViews *xlsxCustomWorkbookViews `xml:"customWorkbookViews"` - PivotCaches *xlsxPivotCaches `xml:"pivotCaches"` - SmartTagPr *xlsxExtLst `xml:"smartTagPr"` - SmartTagTypes *xlsxExtLst `xml:"smartTagTypes"` - WebPublishing *xlsxExtLst `xml:"webPublishing"` - FileRecoveryPr *xlsxFileRecoveryPr `xml:"fileRecoveryPr"` - WebPublishObjects *xlsxExtLst `xml:"webPublishObjects"` - ExtLst *xlsxExtLst `xml:"extLst"` + XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main workbook"` + Conformance string `xml:"conformance,attr,omitempty"` + FileVersion *xlsxFileVersion `xml:"fileVersion"` + FileSharing *xlsxExtLst `xml:"fileSharing"` + AlternateContent *xlsxAlternateContent `xml:"mc:AlternateContent"` + DecodeAlternateContent *xlsxInnerXML `xml:"http://schemas.openxmlformats.org/markup-compatibility/2006 AlternateContent"` + WorkbookPr *xlsxWorkbookPr `xml:"workbookPr"` + WorkbookProtection *xlsxWorkbookProtection `xml:"workbookProtection"` + BookViews *xlsxBookViews `xml:"bookViews"` + Sheets xlsxSheets `xml:"sheets"` + FunctionGroups *xlsxExtLst `xml:"functionGroups"` + ExternalReferences *xlsxExternalReferences `xml:"externalReferences"` + DefinedNames *xlsxDefinedNames `xml:"definedNames"` + CalcPr *xlsxCalcPr `xml:"calcPr"` + OleSize *xlsxExtLst `xml:"oleSize"` + CustomWorkbookViews *xlsxCustomWorkbookViews `xml:"customWorkbookViews"` + PivotCaches *xlsxPivotCaches `xml:"pivotCaches"` + SmartTagPr *xlsxExtLst `xml:"smartTagPr"` + SmartTagTypes *xlsxExtLst `xml:"smartTagTypes"` + WebPublishing *xlsxExtLst `xml:"webPublishing"` + FileRecoveryPr *xlsxFileRecoveryPr `xml:"fileRecoveryPr"` + WebPublishObjects *xlsxExtLst `xml:"webPublishObjects"` + ExtLst *xlsxExtLst `xml:"extLst"` } // xlsxFileRecoveryPr maps sheet recovery information. This element defines diff --git a/xmlWorksheet.go b/xmlWorksheet.go index 4a9c88a9dc..c327d3c4ac 100644 --- a/xmlWorksheet.go +++ b/xmlWorksheet.go @@ -20,46 +20,48 @@ import ( // http://schemas.openxmlformats.org/spreadsheetml/2006/main. type xlsxWorksheet struct { sync.Mutex - XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main worksheet"` - SheetPr *xlsxSheetPr `xml:"sheetPr"` - Dimension *xlsxDimension `xml:"dimension"` - SheetViews *xlsxSheetViews `xml:"sheetViews"` - SheetFormatPr *xlsxSheetFormatPr `xml:"sheetFormatPr"` - Cols *xlsxCols `xml:"cols"` - SheetData xlsxSheetData `xml:"sheetData"` - SheetCalcPr *xlsxInnerXML `xml:"sheetCalcPr"` - SheetProtection *xlsxSheetProtection `xml:"sheetProtection"` - ProtectedRanges *xlsxInnerXML `xml:"protectedRanges"` - Scenarios *xlsxInnerXML `xml:"scenarios"` - AutoFilter *xlsxAutoFilter `xml:"autoFilter"` - SortState *xlsxSortState `xml:"sortState"` - DataConsolidate *xlsxInnerXML `xml:"dataConsolidate"` - CustomSheetViews *xlsxCustomSheetViews `xml:"customSheetViews"` - MergeCells *xlsxMergeCells `xml:"mergeCells"` - PhoneticPr *xlsxPhoneticPr `xml:"phoneticPr"` - ConditionalFormatting []*xlsxConditionalFormatting `xml:"conditionalFormatting"` - DataValidations *xlsxDataValidations `xml:"dataValidations"` - Hyperlinks *xlsxHyperlinks `xml:"hyperlinks"` - PrintOptions *xlsxPrintOptions `xml:"printOptions"` - PageMargins *xlsxPageMargins `xml:"pageMargins"` - PageSetUp *xlsxPageSetUp `xml:"pageSetup"` - HeaderFooter *xlsxHeaderFooter `xml:"headerFooter"` - RowBreaks *xlsxBreaks `xml:"rowBreaks"` - ColBreaks *xlsxBreaks `xml:"colBreaks"` - CustomProperties *xlsxInnerXML `xml:"customProperties"` - CellWatches *xlsxInnerXML `xml:"cellWatches"` - IgnoredErrors *xlsxInnerXML `xml:"ignoredErrors"` - SmartTags *xlsxInnerXML `xml:"smartTags"` - Drawing *xlsxDrawing `xml:"drawing"` - LegacyDrawing *xlsxLegacyDrawing `xml:"legacyDrawing"` - LegacyDrawingHF *xlsxLegacyDrawingHF `xml:"legacyDrawingHF"` - DrawingHF *xlsxDrawingHF `xml:"drawingHF"` - Picture *xlsxPicture `xml:"picture"` - OleObjects *xlsxInnerXML `xml:"oleObjects"` - Controls *xlsxInnerXML `xml:"controls"` - WebPublishItems *xlsxInnerXML `xml:"webPublishItems"` - TableParts *xlsxTableParts `xml:"tableParts"` - ExtLst *xlsxExtLst `xml:"extLst"` + XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main worksheet"` + SheetPr *xlsxSheetPr `xml:"sheetPr"` + Dimension *xlsxDimension `xml:"dimension"` + SheetViews *xlsxSheetViews `xml:"sheetViews"` + SheetFormatPr *xlsxSheetFormatPr `xml:"sheetFormatPr"` + Cols *xlsxCols `xml:"cols"` + SheetData xlsxSheetData `xml:"sheetData"` + SheetCalcPr *xlsxInnerXML `xml:"sheetCalcPr"` + SheetProtection *xlsxSheetProtection `xml:"sheetProtection"` + ProtectedRanges *xlsxInnerXML `xml:"protectedRanges"` + Scenarios *xlsxInnerXML `xml:"scenarios"` + AutoFilter *xlsxAutoFilter `xml:"autoFilter"` + SortState *xlsxSortState `xml:"sortState"` + DataConsolidate *xlsxInnerXML `xml:"dataConsolidate"` + CustomSheetViews *xlsxCustomSheetViews `xml:"customSheetViews"` + MergeCells *xlsxMergeCells `xml:"mergeCells"` + PhoneticPr *xlsxPhoneticPr `xml:"phoneticPr"` + ConditionalFormatting []*xlsxConditionalFormatting `xml:"conditionalFormatting"` + DataValidations *xlsxDataValidations `xml:"dataValidations"` + Hyperlinks *xlsxHyperlinks `xml:"hyperlinks"` + PrintOptions *xlsxPrintOptions `xml:"printOptions"` + PageMargins *xlsxPageMargins `xml:"pageMargins"` + PageSetUp *xlsxPageSetUp `xml:"pageSetup"` + HeaderFooter *xlsxHeaderFooter `xml:"headerFooter"` + RowBreaks *xlsxBreaks `xml:"rowBreaks"` + ColBreaks *xlsxBreaks `xml:"colBreaks"` + CustomProperties *xlsxInnerXML `xml:"customProperties"` + CellWatches *xlsxInnerXML `xml:"cellWatches"` + IgnoredErrors *xlsxInnerXML `xml:"ignoredErrors"` + SmartTags *xlsxInnerXML `xml:"smartTags"` + Drawing *xlsxDrawing `xml:"drawing"` + LegacyDrawing *xlsxLegacyDrawing `xml:"legacyDrawing"` + LegacyDrawingHF *xlsxLegacyDrawingHF `xml:"legacyDrawingHF"` + DrawingHF *xlsxDrawingHF `xml:"drawingHF"` + Picture *xlsxPicture `xml:"picture"` + OleObjects *xlsxInnerXML `xml:"oleObjects"` + Controls *xlsxInnerXML `xml:"controls"` + WebPublishItems *xlsxInnerXML `xml:"webPublishItems"` + TableParts *xlsxTableParts `xml:"tableParts"` + ExtLst *xlsxExtLst `xml:"extLst"` + AlternateContent *xlsxAlternateContent `xml:"mc:AlternateContent"` + DecodeAlternateContent *xlsxInnerXML `xml:"http://schemas.openxmlformats.org/markup-compatibility/2006 AlternateContent"` } // xlsxDrawing change r:id to rid in the namespace. @@ -692,6 +694,16 @@ type xlsxLegacyDrawingHF struct { RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"` } +// xlsxAlternateContent is a container for a sequence of multiple +// representations of a given piece of content. The program reading the file +// should only process one of these, and the one chosen should be based on +// which conditions match. +type xlsxAlternateContent struct { + XMLNSMC string `xml:"xmlns:mc,attr,omitempty"` + Content string `xml:",innerxml"` +} + +// xlsxInnerXML holds parts of XML content currently not unmarshal. type xlsxInnerXML struct { Content string `xml:",innerxml"` }