From 0072bb731043f89ce978778b9d7fdc6160e29de0 Mon Sep 17 00:00:00 2001 From: xuri Date: Fri, 22 Feb 2019 22:17:38 +0800 Subject: [PATCH] resolve the issue corrupted xlsx after deleting formula of cell, reference #346 --- calcchain.go | 55 ++++++++++++++++++++++++++++++++++++++++++++ cell.go | 5 ++++ col.go | 4 ++++ excelize.go | 4 +++- excelize_test.go | 12 +++++++++- file.go | 1 + rows.go | 14 ++++++++++- sheet.go | 2 +- test/CalcChain.xlsx | Bin 0 -> 5959 bytes xmlCalcChain.go | 28 ++++++++++++++++++++++ 10 files changed, 121 insertions(+), 4 deletions(-) create mode 100644 calcchain.go create mode 100755 test/CalcChain.xlsx create mode 100644 xmlCalcChain.go diff --git a/calcchain.go b/calcchain.go new file mode 100644 index 0000000..285a3e9 --- /dev/null +++ b/calcchain.go @@ -0,0 +1,55 @@ +// Copyright 2016 - 2019 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 XLSX files. Support reads and writes XLSX file generated by +// Microsoft Excelâ„¢ 2007 and later. Support save file without losing original +// charts of XLSX. This library needs Go version 1.8 or later. + +package excelize + +import "encoding/xml" + +// calcChainReader provides a function to get the pointer to the structure +// after deserialization of xl/calcChain.xml. +func (f *File) calcChainReader() *xlsxCalcChain { + if f.CalcChain == nil { + var c xlsxCalcChain + _ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML("xl/calcChain.xml")), &c) + f.CalcChain = &c + } + return f.CalcChain +} + +// calcChainWriter provides a function to save xl/calcChain.xml after +// serialize structure. +func (f *File) calcChainWriter() { + if f.CalcChain != nil { + output, _ := xml.Marshal(f.CalcChain) + f.saveFileList("xl/calcChain.xml", output) + } +} + +// deleteCalcChain provides a function to remove cell reference on the +// calculation chain. +func (f *File) deleteCalcChain(axis string) { + calc := f.calcChainReader() + if calc != nil { + for i, c := range calc.C { + if c.R == axis { + calc.C = append(calc.C[:i], calc.C[i+1:]...) + } + } + } + if len(calc.C) == 0 { + f.CalcChain = nil + delete(f.XLSX, "xl/calcChain.xml") + content := f.contentTypesReader() + for k, v := range content.Overrides { + if v.PartName == "/xl/calcChain.xml" { + content.Overrides = append(content.Overrides[:k], content.Overrides[k+1:]...) + } + } + } +} diff --git a/cell.go b/cell.go index afe8635..3cf880a 100644 --- a/cell.go +++ b/cell.go @@ -305,6 +305,11 @@ func (f *File) SetCellFormula(sheet, axis, formula string) { completeRow(xlsx, rows, cell) completeCol(xlsx, rows, cell) + if formula == "" { + xlsx.SheetData.Row[xAxis].C[yAxis].F = nil + f.deleteCalcChain(axis) + return + } if xlsx.SheetData.Row[xAxis].C[yAxis].F != nil { xlsx.SheetData.Row[xAxis].C[yAxis].F.Content = formula } else { diff --git a/col.go b/col.go index af2c321..1130c3a 100644 --- a/col.go +++ b/col.go @@ -322,6 +322,10 @@ func (f *File) InsertCol(sheet, column string) { // // xlsx.RemoveCol("Sheet1", "C") // +// Use this method with caution, which will affect changes in references such +// as formulas, charts, and so on. If there is any referenced value of the +// worksheet, it will cause a file error when you open it. The excelize only +// partially updates these references currently. func (f *File) RemoveCol(sheet, column string) { xlsx := f.workSheetReader(sheet) for r := range xlsx.SheetData.Row { diff --git a/excelize.go b/excelize.go index 32f0451..32aa431 100644 --- a/excelize.go +++ b/excelize.go @@ -25,6 +25,7 @@ import ( type File struct { checked map[string]bool sheetMap map[string]string + CalcChain *xlsxCalcChain ContentTypes *xlsxTypes Path string SharedStrings *xlsxSST @@ -201,7 +202,8 @@ func (f *File) UpdateLinkedValue() { // row: Index number of the row we're inserting/deleting before // offset: Number of rows/column to insert/delete negative values indicate deletion // -// TODO: adjustPageBreaks, adjustComments, adjustDataValidations, adjustProtectedCells +// TODO: adjustCalcChain, adjustPageBreaks, adjustComments, +// adjustDataValidations, adjustProtectedCells // func (f *File) adjustHelper(sheet string, column, row, offset int) { xlsx := f.workSheetReader(sheet) diff --git a/excelize_test.go b/excelize_test.go index ebbfcf7..d621b87 100644 --- a/excelize_test.go +++ b/excelize_test.go @@ -342,8 +342,18 @@ func TestSetCellFormula(t *testing.T) { xlsx.SetCellFormula("Sheet1", "C19", "SUM(Sheet2!D2,Sheet2!D9)") // Test set cell formula with illegal rows number. xlsx.SetCellFormula("Sheet1", "C", "SUM(Sheet2!D2,Sheet2!D9)") + assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestSetCellFormula1.xlsx"))) - assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestSetCellFormula.xlsx"))) + xlsx, err = OpenFile(filepath.Join("test", "CalcChain.xlsx")) + if !assert.NoError(t, err) { + t.FailNow() + } + // Test remove cell formula. + xlsx.SetCellFormula("Sheet1", "A1", "") + assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestSetCellFormula2.xlsx"))) + // Test remove all cell formula. + xlsx.SetCellFormula("Sheet1", "B1", "") + assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestSetCellFormula3.xlsx"))) } func TestSetSheetBackground(t *testing.T) { diff --git a/file.go b/file.go index 3e49803..66b46c5 100644 --- a/file.go +++ b/file.go @@ -92,6 +92,7 @@ func (f *File) WriteToBuffer() (*bytes.Buffer, error) { f.workbookRelsWriter() f.worksheetWriter() f.styleSheetWriter() + f.calcChainWriter() for path, content := range f.XLSX { fi, err := zw.Create(path) if err != nil { diff --git a/rows.go b/rows.go index 3984216..aebc979 100644 --- a/rows.go +++ b/rows.go @@ -343,6 +343,10 @@ func (f *File) GetRowOutlineLevel(sheet string, rowIndex int) uint8 { // // xlsx.RemoveRow("Sheet1", 2) // +// Use this method with caution, which will affect changes in references such +// as formulas, charts, and so on. If there is any referenced value of the +// worksheet, it will cause a file error when you open it. The excelize only +// partially updates these references currently. func (f *File) RemoveRow(sheet string, row int) { if row < 0 { return @@ -375,15 +379,23 @@ func (f *File) InsertRow(sheet string, row int) { // // xlsx.DuplicateRow("Sheet1", 2) // +// Use this method with caution, which will affect changes in references such +// as formulas, charts, and so on. If there is any referenced value of the +// worksheet, it will cause a file error when you open it. The excelize only +// partially updates these references currently. func (f *File) DuplicateRow(sheet string, row int) { f.DuplicateRowTo(sheet, row, row+1) } // DuplicateRowTo inserts a copy of specified row at specified row position -// movig down exists rows aftet target position +// moving down exists rows after target position // // xlsx.DuplicateRowTo("Sheet1", 2, 7) // +// Use this method with caution, which will affect changes in references such +// as formulas, charts, and so on. If there is any referenced value of the +// worksheet, it will cause a file error when you open it. The excelize only +// partially updates these references currently. func (f *File) DuplicateRowTo(sheet string, row, row2 int) { if row <= 0 || row2 <= 0 || row == row2 { return diff --git a/sheet.go b/sheet.go index cb1c08e..efbd466 100644 --- a/sheet.go +++ b/sheet.go @@ -900,7 +900,7 @@ func (p *PageLayoutPaperSize) getPageLayout(ps *xlsxPageSetUp) { // 40 | German standard fanfold (8.5 in. by 12 in.) // 41 | German legal fanfold (8.5 in. by 13 in.) // 42 | ISO B4 (250 mm by 353 mm) -// 43 | Japanese double postcard (200 mm by 148 mm) +// 43 | Japanese postcard (100 mm by 148 mm) // 44 | Standard paper (9 in. by 11 in.) // 45 | Standard paper (10 in. by 11 in.) // 46 | Standard paper (15 in. by 11 in.) diff --git a/test/CalcChain.xlsx b/test/CalcChain.xlsx new file mode 100755 index 0000000000000000000000000000000000000000..8558f82fa79c99e4d81065ec488f3195ac4db5d5 GIT binary patch literal 5959 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 literal 0 HcmV?d00001 diff --git a/xmlCalcChain.go b/xmlCalcChain.go new file mode 100644 index 0000000..9c916bf --- /dev/null +++ b/xmlCalcChain.go @@ -0,0 +1,28 @@ +// Copyright 2016 - 2019 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 XLSX files. Support reads and writes XLSX file generated by +// Microsoft Excelâ„¢ 2007 and later. Support save file without losing original +// charts of XLSX. This library needs Go version 1.8 or later. + +package excelize + +import "encoding/xml" + +// xlsxCalcChain directly maps the calcChain element. This element represents the root of the calculation chain. +type xlsxCalcChain struct { + XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main calcChain"` + C []xlsxCalcChainC `xml:"c"` +} + +// xlsxCalcChainC directly maps the c element. +type xlsxCalcChainC struct { + R string `xml:"r,attr"` + I int `xml:"i,attr"` + L bool `xml:"l,attr,omitempty"` + S bool `xml:"s,attr,omitempty"` + T bool `xml:"t,attr,omitempty"` + A bool `xml:"a,attr,omitempty"` +}