From ecf9194b651813d995fa70dada3f223d0dc92735 Mon Sep 17 00:00:00 2001 From: Dominik Stadler Date: Sun, 18 May 2014 19:18:27 +0000 Subject: [PATCH] Bug 56170: Fix a problem with cells in workbooks becoming disconnected from XMLBeans whenever columns need to be reordered during writing the file. This happens because setCArray() disconnects any previously stored array-item but we try to re-use them. So we need to recreate the CTCell and set it in the XSSFCell to make this work in all currently tested cases. git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1595659 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/poi/xssf/usermodel/XSSFCell.java | 12 +- .../apache/poi/xssf/usermodel/XSSFRow.java | 19 ++- .../poi/xssf/usermodel/TestXSSFBugs.java | 125 +++++++++--------- .../poi/xssf/usermodel/TestXSSFCell.java | 109 ++++++++++++++- test-data/spreadsheet/56170.xlsx | Bin 0 -> 8577 bytes 5 files changed, 194 insertions(+), 71 deletions(-) create mode 100644 test-data/spreadsheet/56170.xlsx diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFCell.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFCell.java index 47df620a58..9675b9a14b 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFCell.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFCell.java @@ -70,7 +70,7 @@ public final class XSSFCell implements Cell { * the xml bean containing information about the cell's location, value, * data type, formatting, and formula */ - private final CTCell _cell; + private CTCell _cell; /** * the XSSFRow this cell belongs to @@ -940,6 +940,16 @@ public final class XSSFCell implements Cell { public CTCell getCTCell(){ return _cell; } + + /** + * Set a new internal xml bean. This is only for internal use, do not call this from outside! + * + * This is necessary in some rare cases to work around XMLBeans specialties. + */ + @Internal + public void setCTCell(CTCell cell) { + _cell = cell; + } /** * Chooses a new boolean value for the cell when its type is changing.

diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFRow.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFRow.java index 75e17092a9..f21b1aa6d2 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFRow.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFRow.java @@ -18,6 +18,7 @@ package org.apache.poi.xssf.usermodel; import java.util.Iterator; +import java.util.Map; import java.util.TreeMap; import org.apache.poi.ss.SpreadsheetVersion; @@ -441,8 +442,9 @@ public class XSSFRow implements Row, Comparable { protected void onDocumentWrite(){ // check if cells in the CTRow are ordered boolean isOrdered = true; - if(_row.sizeOfCArray() != _cells.size()) isOrdered = false; - else { + if(_row.sizeOfCArray() != _cells.size()) { + isOrdered = false; + } else { int i = 0; for (XSSFCell cell : _cells.values()) { CTCell c1 = cell.getCTCell(); @@ -460,9 +462,18 @@ public class XSSFRow implements Row, Comparable { if(!isOrdered){ CTCell[] cArray = new CTCell[_cells.size()]; int i = 0; - for (XSSFCell c : _cells.values()) { - cArray[i++] = c.getCTCell(); + for (Map.Entry entry : _cells.entrySet()) { + cArray[i] = (CTCell) entry.getValue().getCTCell().copy(); + + // we have to copy and re-create the XSSFCell here because the + // elements as otherwise setCArray below invalidates all the columns! + // see Bug 56170, XMLBeans seems to always release previous objects + // in the CArray, so we need to provide completely new ones here! + //_cells.put(entry.getKey(), new XSSFCell(this, cArray[i])); + entry.getValue().setCTCell(cArray[i]); + i++; } + _row.setCArray(cArray); } } diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFBugs.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFBugs.java index f0266c9f69..9af149bf56 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFBugs.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFBugs.java @@ -18,13 +18,7 @@ package org.apache.poi.xssf.usermodel; import static org.hamcrest.core.IsEqual.equalTo; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.Assert.*; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -43,24 +37,7 @@ import org.apache.poi.ss.formula.WorkbookEvaluator; import org.apache.poi.ss.formula.eval.ErrorEval; import org.apache.poi.ss.formula.eval.ValueEval; import org.apache.poi.ss.formula.functions.Function; -import org.apache.poi.ss.usermodel.BaseTestBugzillaIssues; -import org.apache.poi.ss.usermodel.Cell; -import org.apache.poi.ss.usermodel.CellStyle; -import org.apache.poi.ss.usermodel.CellValue; -import org.apache.poi.ss.usermodel.ClientAnchor; -import org.apache.poi.ss.usermodel.Comment; -import org.apache.poi.ss.usermodel.CreationHelper; -import org.apache.poi.ss.usermodel.DataFormatter; -import org.apache.poi.ss.usermodel.Drawing; -import org.apache.poi.ss.usermodel.Font; -import org.apache.poi.ss.usermodel.FormulaError; -import org.apache.poi.ss.usermodel.FormulaEvaluator; -import org.apache.poi.ss.usermodel.IndexedColors; -import org.apache.poi.ss.usermodel.Name; -import org.apache.poi.ss.usermodel.Row; -import org.apache.poi.ss.usermodel.Sheet; -import org.apache.poi.ss.usermodel.Workbook; -import org.apache.poi.ss.usermodel.WorkbookFactory; +import org.apache.poi.ss.usermodel.*; import org.apache.poi.ss.util.AreaReference; import org.apache.poi.ss.util.CellRangeAddress; import org.apache.poi.ss.util.CellReference; @@ -601,47 +578,77 @@ public final class TestXSSFBugs extends BaseTestBugzillaIssues { } /** - * Various ways of removing a cell formula should all zap - * the calcChain entry. + * Various ways of removing a cell formula should all zap the calcChain + * entry. */ @Test public void bug49966() throws Exception { - XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("shared_formulas.xlsx"); - XSSFSheet sheet = wb.getSheetAt(0); - - // CalcChain has lots of entries - CalculationChain cc = wb.getCalculationChain(); - assertEquals("A2", cc.getCTCalcChain().getCArray(0).getR()); - assertEquals("A3", cc.getCTCalcChain().getCArray(1).getR()); - assertEquals("A4", cc.getCTCalcChain().getCArray(2).getR()); - assertEquals("A5", cc.getCTCalcChain().getCArray(3).getR()); - assertEquals("A6", cc.getCTCalcChain().getCArray(4).getR()); - assertEquals("A7", cc.getCTCalcChain().getCArray(5).getR()); - assertEquals("A8", cc.getCTCalcChain().getCArray(6).getR()); - assertEquals(40, cc.getCTCalcChain().sizeOfCArray()); + XSSFWorkbook wb = XSSFTestDataSamples + .openSampleWorkbook("shared_formulas.xlsx"); + XSSFSheet sheet = wb.getSheetAt(0); - // Try various ways of changing the formulas - // If it stays a formula, chain entry should remain - // Otherwise should go - sheet.getRow(1).getCell(0).setCellFormula("A1"); // stay - sheet.getRow(2).getCell(0).setCellFormula(null); // go - sheet.getRow(3).getCell(0).setCellType(Cell.CELL_TYPE_FORMULA); // stay - sheet.getRow(4).getCell(0).setCellType(Cell.CELL_TYPE_STRING); // go - sheet.getRow(5).removeCell( - sheet.getRow(5).getCell(0) // go - ); - sheet.getRow(6).getCell(0).setCellType(Cell.CELL_TYPE_BLANK); // go - sheet.getRow(7).getCell(0).setCellValue((String)null); // go + Workbook wbRead = XSSFTestDataSamples.writeOutAndReadBack(wb); - // Save and check - wb = XSSFTestDataSamples.writeOutAndReadBack(wb); - assertEquals(35, cc.getCTCalcChain().sizeOfCArray()); + // CalcChain has lots of entries + CalculationChain cc = wb.getCalculationChain(); + assertEquals("A2", cc.getCTCalcChain().getCArray(0).getR()); + assertEquals("A3", cc.getCTCalcChain().getCArray(1).getR()); + assertEquals("A4", cc.getCTCalcChain().getCArray(2).getR()); + assertEquals("A5", cc.getCTCalcChain().getCArray(3).getR()); + assertEquals("A6", cc.getCTCalcChain().getCArray(4).getR()); + assertEquals("A7", cc.getCTCalcChain().getCArray(5).getR()); + assertEquals("A8", cc.getCTCalcChain().getCArray(6).getR()); + assertEquals(40, cc.getCTCalcChain().sizeOfCArray()); - cc = wb.getCalculationChain(); - assertEquals("A2", cc.getCTCalcChain().getCArray(0).getR()); - assertEquals("A4", cc.getCTCalcChain().getCArray(1).getR()); - assertEquals("A9", cc.getCTCalcChain().getCArray(2).getR()); + wbRead = XSSFTestDataSamples.writeOutAndReadBack(wb); + // Try various ways of changing the formulas + // If it stays a formula, chain entry should remain + // Otherwise should go + sheet.getRow(1).getCell(0).setCellFormula("A1"); // stay + sheet.getRow(2).getCell(0).setCellFormula(null); // go + sheet.getRow(3).getCell(0).setCellType(Cell.CELL_TYPE_FORMULA); // stay + wbRead = XSSFTestDataSamples.writeOutAndReadBack(wb); + sheet.getRow(4).getCell(0).setCellType(Cell.CELL_TYPE_STRING); // go + wbRead = XSSFTestDataSamples.writeOutAndReadBack(wb); + + validateCells(sheet); + sheet.getRow(5).removeCell(sheet.getRow(5).getCell(0)); // go + validateCells(sheet); + wbRead = XSSFTestDataSamples.writeOutAndReadBack(wb); + + sheet.getRow(6).getCell(0).setCellType(Cell.CELL_TYPE_BLANK); // go + wbRead = XSSFTestDataSamples.writeOutAndReadBack(wb); + sheet.getRow(7).getCell(0).setCellValue((String) null); // go + + wbRead = XSSFTestDataSamples.writeOutAndReadBack(wb); + + // Save and check + wb = XSSFTestDataSamples.writeOutAndReadBack(wb); + assertEquals(35, cc.getCTCalcChain().sizeOfCArray()); + + cc = wb.getCalculationChain(); + assertEquals("A2", cc.getCTCalcChain().getCArray(0).getR()); + assertEquals("A4", cc.getCTCalcChain().getCArray(1).getR()); + assertEquals("A9", cc.getCTCalcChain().getCArray(2).getR()); + } + + @Test + public void bug49966Row() throws Exception { + XSSFWorkbook wb = XSSFTestDataSamples + .openSampleWorkbook("shared_formulas.xlsx"); + XSSFSheet sheet = wb.getSheetAt(0); + + validateCells(sheet); + sheet.getRow(5).removeCell(sheet.getRow(5).getCell(0)); // go + validateCells(sheet); + } + + private void validateCells(XSSFSheet sheet) { + for(Row row : sheet) { + // trigger handling + ((XSSFRow)row).onDocumentWrite(); + } } @Test diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFCell.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFCell.java index 44583fdf66..6d91fcd889 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFCell.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFCell.java @@ -17,15 +17,22 @@ package org.apache.poi.xssf.usermodel; -import org.apache.poi.ss.usermodel.*; -import org.apache.poi.xssf.XSSFITestDataProvider; -import org.apache.poi.xssf.model.SharedStringsTable; -import org.openxmlformats.schemas.spreadsheetml.x2006.main.STCellType; -import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTCell; - -import java.io.FileOutputStream; import java.io.IOException; +import org.apache.poi.ss.usermodel.BaseTestCell; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.DataFormatter; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.ss.util.CellReference; +import org.apache.poi.xssf.XSSFITestDataProvider; +import org.apache.poi.xssf.XSSFTestDataSamples; +import org.apache.poi.xssf.model.SharedStringsTable; +import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTCell; +import org.openxmlformats.schemas.spreadsheetml.x2006.main.STCellType; + /** * @author Yegor Kozlov */ @@ -276,5 +283,93 @@ public final class TestXSSFCell extends BaseTestCell { } } } + + public void test56170() throws IOException { + final Workbook wb = XSSFTestDataSamples.openSampleWorkbook("56170.xlsx"); + final XSSFSheet sheet = (XSSFSheet) wb.getSheetAt(0); + Workbook wbRead = XSSFTestDataSamples.writeOutAndReadBack(wb); + Cell cell; + + // add some contents to table so that the table will need expansion + Row row = sheet.getRow(0); + wbRead = XSSFTestDataSamples.writeOutAndReadBack(wb); + cell = row.createCell(0); + wbRead = XSSFTestDataSamples.writeOutAndReadBack(wb); + cell.setCellValue("demo1"); + wbRead = XSSFTestDataSamples.writeOutAndReadBack(wb); + cell = row.createCell(1); + wbRead = XSSFTestDataSamples.writeOutAndReadBack(wb); + cell.setCellValue("demo2"); + wbRead = XSSFTestDataSamples.writeOutAndReadBack(wb); + cell = row.createCell(2); + wbRead = XSSFTestDataSamples.writeOutAndReadBack(wb); + cell.setCellValue("demo3"); + + wbRead = XSSFTestDataSamples.writeOutAndReadBack(wb); + + row = sheet.getRow(1); + cell = row.createCell(0); + cell.setCellValue("demo1"); + cell = row.createCell(1); + cell.setCellValue("demo2"); + cell = row.createCell(2); + cell.setCellValue("demo3"); + + wbRead = XSSFTestDataSamples.writeOutAndReadBack(wb); + + // expand table + XSSFTable table = sheet.getTables().get(0); + final CellReference startRef = table.getStartCellReference(); + final CellReference endRef = table.getEndCellReference(); + table.getCTTable().setRef(new CellRangeAddress(startRef.getRow(), 1, startRef.getCol(), endRef.getCol()).formatAsString()); + + wbRead = XSSFTestDataSamples.writeOutAndReadBack(wb); + assertNotNull(wbRead); + + /*FileOutputStream stream = new FileOutputStream("c:\\temp\\output.xlsx"); + workbook.write(stream); + stream.close();*/ + } + + public void test56170Reproduce() throws IOException { + final Workbook wb = new XSSFWorkbook(); + final Sheet sheet = wb.createSheet(); + Row row = sheet.createRow(0); + + // by creating Cells out of order we trigger the handling in onDocumentWrite() + Cell cell1 = row.createCell(1); + Cell cell2 = row.createCell(0); + + validateRow(row); + + validateRow(row); + + // once again with removing one cell + row.removeCell(cell1); + + validateRow(row); + + // once again with removing one cell + row.removeCell(cell1); + + // now check again + validateRow(row); + + // once again with removing one cell + row.removeCell(cell2); + + // now check again + validateRow(row); + } + + private void validateRow(Row row) { + // trigger bug with CArray handling + ((XSSFRow)row).onDocumentWrite(); + + for(Cell cell : row) { + cell.toString(); + } + } + } diff --git a/test-data/spreadsheet/56170.xlsx b/test-data/spreadsheet/56170.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..f80e386cc31e7097c2e17ac740ae300d2e52f60e GIT binary patch literal 8577 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 z zFvpD(x(7VM3*C)LFJ}#v&>73;0M&G$b`A|kKVp%xREXDQ+&tJ;J~B(P&vmMM`IPZ= zLR?(Ex?xs;*^WnM2ZG2?O1A_jm(%IZ>ZZ>1qdlrD=Y3u0b%2n%jM;@?FI!#lr^0r2N#Jev7S3DWy-VC`?R6y^!5ook?L$~r?qjD0i6issD|~q z0j6%=fY7I&Ku>uk+@mo{QG42uNV2({a@zHYD)i9@XwLgUxGjGg-EviZ2)#%@SjtFzY7i{`&9q$nZR{D z*E_?%G@)Ys)+fF$e7(W>OV|_iQ~0-b=XDL&yNbUw$YTHJ=l_2_#_I~Ncj|sAl*0L` z@Ot0wx`OMK(k}%-Wakg**k5(ib