diff --git a/src/documentation/content/xdocs/changes.xml b/src/documentation/content/xdocs/changes.xml index be8df65763..bc95d9b7a2 100644 --- a/src/documentation/content/xdocs/changes.xml +++ b/src/documentation/content/xdocs/changes.xml @@ -36,6 +36,7 @@ + 44233 - Support for getting and setting a flag on the sheet, which tells excel to re-calculate all formulas on it at next reload 44201 - Enable cloning of sheets with data validation rules 44200 - Enable cloning of sheets with notes 43008 - Add a moveCell method to HSSFRow, and deprecate setCellNum(), which didn't update things properly diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 5868b0eb2d..122cab85c3 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -33,6 +33,7 @@ + 44233 - Support for getting and setting a flag on the sheet, which tells excel to re-calculate all formulas on it at next reload 44201 - Enable cloning of sheets with data validation rules 44200 - Enable cloning of sheets with notes 43008 - Add a moveCell method to HSSFRow, and deprecate setCellNum(), which didn't update things properly diff --git a/src/java/org/apache/poi/hssf/model/Sheet.java b/src/java/org/apache/poi/hssf/model/Sheet.java index 59e5de3246..f3f7deba07 100644 --- a/src/java/org/apache/poi/hssf/model/Sheet.java +++ b/src/java/org/apache/poi/hssf/model/Sheet.java @@ -97,6 +97,8 @@ public class Sheet implements Model protected ScenarioProtectRecord scenprotect = null; protected PasswordRecord password = null; + /** Add an UncalcedRecord if not true indicating formulas have not been calculated */ + protected boolean uncalced = false; public static final byte PANE_LOWER_RIGHT = (byte)0; public static final byte PANE_UPPER_RIGHT = (byte)1; @@ -161,6 +163,9 @@ public class Sheet implements Model break; } } + else if (rec.getSid() == UncalcedRecord.sid) { + retval.uncalced = true; + } else if (rec.getSid() == DimensionsRecord.sid) { // Make a columns aggregate if one hasn't ready been created. @@ -736,8 +741,14 @@ public class Sheet implements Model { Record record = (( Record ) records.get(k)); - //Once the rows have been found in the list of records, start - //writing out the blocked row information. This includes the DBCell references + // Don't write out UncalcedRecord entries, as + // we handle those specially just below + if (record instanceof UncalcedRecord) { + continue; + } + + // Once the rows have been found in the list of records, start + // writing out the blocked row information. This includes the DBCell references if (record instanceof RowRecordsAggregate) { pos += ((RowRecordsAggregate)record).serialize(pos, data, cells); // rec.length; } else if (record instanceof ValueRecordsAggregate) { @@ -745,8 +756,14 @@ public class Sheet implements Model } else { pos += record.serialize(pos, data ); // rec.length; } - //If the BOF record was just serialized then add the IndexRecord + + // If the BOF record was just serialized then add the IndexRecord if (record.getSid() == BOFRecord.sid) { + // Add an optional UncalcedRecord + if (uncalced) { + UncalcedRecord rec = new UncalcedRecord(); + pos += rec.serialize(pos, data); + } //Can there be more than one BOF for a sheet? If not then we can //remove this guard. So be safe it is left here. if (rows != null && !haveSerializedIndex) { @@ -2184,6 +2201,11 @@ public class Sheet implements Model retval += 2; } } + // Add space for UncalcedRecord + if (uncalced) { + retval += UncalcedRecord.getStaticRecordSize(); + } + return retval; } @@ -2651,8 +2673,22 @@ public class Sheet implements Model public boolean isDisplayRowColHeadings() { return windowTwo.getDisplayRowColHeadings(); } + /** + * @return whether an uncalced record must be inserted or not at generation + */ + public boolean getUncalced() { + return uncalced; + } + /** + * @param uncalced whether an uncalced record must be inserted or not at generation + */ + public void setUncalced(boolean uncalced) { + this.uncalced = uncalced; + } + + /** * Returns the array of margins. If not created, will create. * * @return the array of marings. diff --git a/src/java/org/apache/poi/hssf/record/RecordFactory.java b/src/java/org/apache/poi/hssf/record/RecordFactory.java index cf705a316d..20e8ba788a 100644 --- a/src/java/org/apache/poi/hssf/record/RecordFactory.java +++ b/src/java/org/apache/poi/hssf/record/RecordFactory.java @@ -76,7 +76,7 @@ public class RecordFactory WriteProtectRecord.class, FilePassRecord.class, PaneRecord.class, NoteRecord.class, ObjectProtectRecord.class, ScenarioProtectRecord.class, FileSharingRecord.class, ChartTitleFormatRecord.class, - DVRecord.class, DVALRecord.class + DVRecord.class, DVALRecord.class, UncalcedRecord.class }; } private static Map recordsMap = recordsToMap(records); diff --git a/src/java/org/apache/poi/hssf/record/UncalcedRecord.java b/src/java/org/apache/poi/hssf/record/UncalcedRecord.java new file mode 100644 index 0000000000..c3243f2585 --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/UncalcedRecord.java @@ -0,0 +1,81 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hssf.record; + +import org.apache.poi.util.LittleEndian; + +/** + * Title: Uncalced Record + *

+ * If this record occurs in the Worksheet Substream, it indicates that the formulas have not + * been recalculated before the document was saved. + * + * @author Olivier Leprince + */ + +public class UncalcedRecord extends Record +{ + public final static short sid = 0x5E; + + /** + * Default constructor + */ + public UncalcedRecord() { + } + /** + * read constructor + */ + public UncalcedRecord(RecordInputStream in) { + super(in); + } + + public short getSid() { + return sid; + } + + protected void validateSid(short id) { + if (id != sid) { + throw new RecordFormatException("NOT AN UNCALCED RECORD"); + } + } + + protected void fillFields(RecordInputStream in) { + } + + public String toString() { + StringBuffer buffer = new StringBuffer(); + buffer.append("[UNCALCED]\n"); + buffer.append("[/UNCALCED]\n"); + return buffer.toString(); + } + + public int serialize(int offset, byte[] data) { + LittleEndian.putShort(data, 0 + offset, sid); + LittleEndian.putShort(data, 2 + offset, (short) 2); + LittleEndian.putShort(data, 4 + offset, (short) 0); // unused + return getRecordSize(); + } + + public int getRecordSize() { + return UncalcedRecord.getStaticRecordSize(); + } + + public static int getStaticRecordSize() { + return 6; + } +} diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java b/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java index 7fe6ea9dcb..bb779fef36 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java @@ -594,6 +594,26 @@ public class HSSFSheet region.getColumnTo()); } + /** + * Whether a record must be inserted or not at generation to indicate that + * formula must be recalculated when workbook is opened. + * @param value true if an uncalced record must be inserted or not at generation + */ + public void setForceFormulaRecalculation(boolean value) + { + sheet.setUncalced(value); + } + /** + * Whether a record must be inserted or not at generation to indicate that + * formula must be recalculated when workbook is opened. + * @return true if an uncalced record must be inserted or not at generation + */ + public boolean getForceFormulaRecalculation() + { + return sheet.getUncalced(); + } + + /** * determines whether the output is vertically centered on the page. * @param value true to vertically center, false otherwise. diff --git a/src/testcases/org/apache/poi/hssf/data/TestDataValidation.xls b/src/testcases/org/apache/poi/hssf/data/TestDataValidation.xls index 6b71a77f2d..556ea81927 100644 Binary files a/src/testcases/org/apache/poi/hssf/data/TestDataValidation.xls and b/src/testcases/org/apache/poi/hssf/data/TestDataValidation.xls differ diff --git a/src/testcases/org/apache/poi/hssf/data/UncalcedRecord.xls b/src/testcases/org/apache/poi/hssf/data/UncalcedRecord.xls new file mode 100644 index 0000000000..f1a0396c00 Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/UncalcedRecord.xls differ diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheet.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheet.java index f0deb68e0c..5523638d9a 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheet.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheet.java @@ -19,14 +19,18 @@ package org.apache.poi.hssf.usermodel; -import java.io.*; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; import junit.framework.TestCase; import org.apache.poi.hssf.model.Sheet; import org.apache.poi.hssf.record.HCenterRecord; -import org.apache.poi.hssf.record.ProtectRecord; import org.apache.poi.hssf.record.PasswordRecord; +import org.apache.poi.hssf.record.ProtectRecord; import org.apache.poi.hssf.record.SCLRecord; import org.apache.poi.hssf.record.VCenterRecord; import org.apache.poi.hssf.record.WSBoolRecord; @@ -790,7 +794,81 @@ public class TestHSSFSheet assertTrue(sheet3.getColumnWidth((short)0) <= maxWithRow1And2); } - public static void main(java.lang.String[] args) { + /** + * Setting ForceFormulaRecalculation on sheets + */ + public void testForceRecalculation() throws Exception { + String filename = System.getProperty("HSSF.testdata.path"); + filename = filename + "/UncalcedRecord.xls"; + HSSFWorkbook workbook = new HSSFWorkbook(new FileInputStream(filename)); + + HSSFSheet sheet = workbook.getSheetAt(0); + HSSFSheet sheet2 = workbook.getSheetAt(0); + HSSFRow row = sheet.getRow(0); + row.createCell((short) 0).setCellValue(5); + row.createCell((short) 1).setCellValue(8); + assertFalse(sheet.getForceFormulaRecalculation()); + assertFalse(sheet2.getForceFormulaRecalculation()); + + // Save and manually verify that on column C we have 0, value in template + File tempFile = new File(System.getProperty("java.io.tmpdir")+"/uncalced_err.xls" ); + tempFile.delete(); + FileOutputStream fout = new FileOutputStream( tempFile ); + workbook.write( fout ); + fout.close(); + sheet.setForceFormulaRecalculation(true); + assertTrue(sheet.getForceFormulaRecalculation()); + + // Save and manually verify that on column C we have now 13, calculated value + tempFile = new File(System.getProperty("java.io.tmpdir")+"/uncalced_succ.xls" ); + tempFile.delete(); + fout = new FileOutputStream( tempFile ); + workbook.write( fout ); + fout.close(); + + // Try it can be opened + HSSFWorkbook wb2 = new HSSFWorkbook(new FileInputStream(tempFile)); + + // And check correct sheet settings found + sheet = wb2.getSheetAt(0); + sheet2 = wb2.getSheetAt(1); + assertTrue(sheet.getForceFormulaRecalculation()); + assertFalse(sheet2.getForceFormulaRecalculation()); + + // Now turn if back off again + sheet.setForceFormulaRecalculation(false); + + fout = new FileOutputStream( tempFile ); + wb2.write( fout ); + fout.close(); + wb2 = new HSSFWorkbook(new FileInputStream(tempFile)); + + assertFalse(wb2.getSheetAt(0).getForceFormulaRecalculation()); + assertFalse(wb2.getSheetAt(1).getForceFormulaRecalculation()); + assertFalse(wb2.getSheetAt(2).getForceFormulaRecalculation()); + + // Now add a new sheet, and check things work + // with old ones unset, new one set + HSSFSheet s4 = wb2.createSheet(); + s4.setForceFormulaRecalculation(true); + + assertFalse(sheet.getForceFormulaRecalculation()); + assertFalse(sheet2.getForceFormulaRecalculation()); + assertTrue(s4.getForceFormulaRecalculation()); + + fout = new FileOutputStream( tempFile ); + wb2.write( fout ); + fout.close(); + + HSSFWorkbook wb3 = new HSSFWorkbook(new FileInputStream(tempFile)); + assertFalse(wb3.getSheetAt(0).getForceFormulaRecalculation()); + assertFalse(wb3.getSheetAt(1).getForceFormulaRecalculation()); + assertFalse(wb3.getSheetAt(2).getForceFormulaRecalculation()); + assertTrue(wb3.getSheetAt(3).getForceFormulaRecalculation()); + } + + + public static void main(java.lang.String[] args) { junit.textui.TestRunner.run(TestHSSFSheet.class); } }