diff --git a/src/documentation/content/xdocs/changes.xml b/src/documentation/content/xdocs/changes.xml index a2bd40d73..51aeb2246 100644 --- a/src/documentation/content/xdocs/changes.xml +++ b/src/documentation/content/xdocs/changes.xml @@ -36,6 +36,8 @@ + 44254 - Avoid some unread byte warnings, and properly understand DVALRecord + Add another formula evaluation method, evaluateFormulaCell(cell), which will re-calculate the value for a formula, without affecting the formula itself. 41726 - Fix how we handle signed cell offsets in relative areas and references 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 diff --git a/src/documentation/content/xdocs/hssf/eval.xml b/src/documentation/content/xdocs/hssf/eval.xml index 1416ad7c2..8d6351217 100644 --- a/src/documentation/content/xdocs/hssf/eval.xml +++ b/src/documentation/content/xdocs/hssf/eval.xml @@ -55,10 +55,12 @@

The following code demonstrates how to use the HSSFFormulaEvaluator in the context of other POI excel reading code.

-

There are two ways in which you can use the HSSFFormulaEvalutator API.

+

There are several ways in which you can use the HSSFFormulaEvalutator API.

Using HSSFFormulaEvaluator.<strong>evaluate</strong>(HSSFCell cell) +

This evaluates a given cell, and returns the new value, + without affecting the cell

FileInputStream fis = new FileInputStream("c:/temp/test.xls"); HSSFWorkbook wb = new HSSFWorkbook(fis); @@ -102,12 +104,60 @@ switch (cellValue.getCellType()) {

+ +
Using HSSFFormulaEvaluator.<strong>evaluateFormulaCell</strong>(HSSFCell cell) +

evaluateFormulaCell(HSSFCell cell) + will check to see if the supplied cell is a formula cell. + If it isn't, then no changes will be made to it. If it is, + then the formula is evaluated. The value for the formula + is saved alongside it, to be displayed in excel. The + formula remains in the cell, just with a new value

+

The return of the function is the type of the + formula result, such as HSSFCell.CELL_TYPE_BOOLEAN

+ +FileInputStream fis = new FileInputStream("/somepath/test.xls"); +HSSFWorkbook wb = new HSSFWorkbook(fis); +HSSFSheet sheet = wb.getSheetAt(0); +HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(sheet, wb); + +// suppose your formula is in B3 +CellReference cellReference = new CellReference("B3"); +HSSFRow row = sheet.getRow(cellReference.getRow()); +HSSFCell cell = row.getCell(cellReference.getCol()); +evaluator.setCurrentRow(row); + +if (cell!=null) { + switch (evaluator.evaluateFormulaCell(cell)) { + case HSSFCell.CELL_TYPE_BOOLEAN: + System.out.println(cell.getBooleanCellValue()); + break; + case HSSFCell.CELL_TYPE_NUMERIC: + System.out.println(cell.getNumberCellValue()); + break; + case HSSFCell.CELL_TYPE_STRING: + System.out.println(cell.getStringCellValue()); + break; + case HSSFCell.CELL_TYPE_BLANK: + break; + case HSSFCell.CELL_TYPE_ERROR: + System.out.println(cell.getErrorCellValue()); + break; + + // CELL_TYPE_FORMULA will never occur + case HSSFCell.CELL_TYPE_FORMULA: + break; + } +} + +
+
Using HSSFFormulaEvaluator.<strong>evaluateInCell</strong>(HSSFCell cell)

evaluateInCell(HSSFCell cell) will check to see if the supplied cell is a formula cell. If it isn't, then no changes will be made to it. If it is, then the - formula is evaluated, and the new value saved into the cell.

+ formula is evaluated, and the new value saved into the cell, + in place of the old formula.

FileInputStream fis = new FileInputStream("/somepath/test.xls"); HSSFWorkbook wb = new HSSFWorkbook(fis); @@ -154,14 +204,14 @@ for(int sheetNum = 0; sheetNum < wb.getNumberOfSheets(); sheetNum++) { HSSFSheet sheet = wb.getSheetAt(sheetNum); HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(sheet, wb); - for(Iterator rit = s.rowIterator(); rit.hasNext();) { + for(Iterator rit = sheet.rowIterator(); rit.hasNext();) { HSSFRow r = (HSSFRow)rit.next(); evaluator.setCurrentRow(r); for(Iterator cit = r.cellIterator(); cit.hasNext();) { HSSFCell c = (HSSFCell)cit.next(); if(c.getCellType() == HSSFCell.CELL_TYPE_FORMULA) { - evaluator.evaluateInCell(c); + evaluator.evaluateFormulaCell(c); } } } diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 56b868b87..feabdf76b 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -33,6 +33,8 @@ + 44254 - Avoid some unread byte warnings, and properly understand DVALRecord + Add another formula evaluation method, evaluateFormulaCell(cell), which will re-calculate the value for a formula, without affecting the formula itself. 41726 - Fix how we handle signed cell offsets in relative areas and references 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 diff --git a/src/java/org/apache/poi/hssf/record/DVALRecord.java b/src/java/org/apache/poi/hssf/record/DVALRecord.java index 858f525ca..2846f5066 100644 --- a/src/java/org/apache/poi/hssf/record/DVALRecord.java +++ b/src/java/org/apache/poi/hssf/record/DVALRecord.java @@ -29,19 +29,22 @@ import org.apache.poi.util.LittleEndian; public class DVALRecord extends Record { - public final static short sid = 0x01B2; + public final static short sid = 0x01B2; - //unknown field ; it's size should be 10 - private short field_unknown = 0x0000; + /** Options of the DVAL */ + private short field_1_options; + /** Horizontal position of the dialog */ + private int field_2_horiz_pos; + /** Vertical position of the dialog */ + private int field_3_vert_pos; - //Object ID of the drop down arrow object for list boxes ; - //in our case this will be always FFFF , until - //MSODrawingGroup and MSODrawing records are implemented - private int field_cbo_id = 0xFFFFFFFF; + /** Object ID of the drop down arrow object for list boxes ; + * in our case this will be always FFFF , until + * MSODrawingGroup and MSODrawing records are implemented */ + private int field_cbo_id = 0xFFFFFFFF; - //Number of following DV records - //Default value is 1 - private int field_3_dv_no = 0x00000000; + /** Number of following DV Records */ + private int field_5_dv_no = 0x00000000; public DVALRecord() { @@ -66,17 +69,38 @@ public class DVALRecord extends Record } } - protected void fillFields(RecordInputStream in) - { - for ( int i=0; i<5; i++) - { - this.field_unknown = in.readShort(); - } + protected void fillFields(RecordInputStream in) + { + this.field_1_options = in.readShort(); + this.field_2_horiz_pos = in.readInt(); + this.field_3_vert_pos = in.readInt(); this.field_cbo_id = in.readInt(); - this.field_3_dv_no = in.readInt(); - } + this.field_5_dv_no = in.readInt(); + } + /** + * @param field_1_options the options of the dialog + */ + public void setOptions(short field_1_options) { + this.field_1_options = field_1_options; + } + + /** + * @param field_2_horiz_pos the Horizontal position of the dialog + */ + public void setHorizontalPos(int field_2_horiz_pos) { + this.field_2_horiz_pos = field_2_horiz_pos; + } + + /** + * @param field_3_vert_pos the Vertical position of the dialog + */ + public void setVerticalPos(int field_3_vert_pos) { + this.field_3_vert_pos = field_3_vert_pos; + } + + /** * set the object ID of the drop down arrow object for list boxes * @param cboID - Object ID */ @@ -91,10 +115,33 @@ public class DVALRecord extends Record */ public void setDVRecNo(int dvNo) { - this.field_3_dv_no = dvNo; + this.field_5_dv_no = dvNo; } + + /** + * @return the field_1_options + */ + public short getOptions() { + return field_1_options; + } + + /** + * @return the Horizontal position of the dialog + */ + public int getHorizontalPos() { + return field_2_horiz_pos; + } + + /** + * @return the the Vertical position of the dialog + */ + public int getVerticalPos() { + return field_3_vert_pos; + } + + /** * get Object ID of the drop down arrow object for list boxes */ public int getObjectID( ) @@ -107,29 +154,32 @@ public class DVALRecord extends Record */ public int getDVRecNo( ) { - return this.field_3_dv_no; + return this.field_5_dv_no; } - public String toString() - { - StringBuffer buffer = new StringBuffer(); + public String toString() + { + StringBuffer buffer = new StringBuffer(); - buffer.append("[DVAL]\n"); - buffer.append(" .comboObjectID = ").append(Integer.toHexString(this.getObjectID())).append("\n"); - buffer.append(" .DVRecordsNumber = ").append(Integer.toHexString(this.getDVRecNo())).append("\n"); - buffer.append("[/DVAL]\n"); - return buffer.toString(); - } + buffer.append("[DVAL]\n"); + buffer.append(" .options = ").append(this.getOptions()).append('\n'); + buffer.append(" .horizPos = ").append(this.getHorizontalPos()).append('\n'); + buffer.append(" .vertPos = ").append(this.getVerticalPos()).append('\n'); + buffer.append(" .comboObjectID = ").append(Integer.toHexString(this.getObjectID())).append("\n"); + buffer.append(" .DVRecordsNumber = ").append(Integer.toHexString(this.getDVRecNo())).append("\n"); + buffer.append("[/DVAL]\n"); + return buffer.toString(); + } public int serialize(int offset, byte [] data) { LittleEndian.putShort(data, 0 + offset, this.sid); LittleEndian.putShort(data, 2 + offset, ( short)(this.getRecordSize()-4)); - for ( int i=0; i<5; i++) - { - LittleEndian.putShort(data, 4 + i*2 + offset, (short)this.field_unknown); - } + + LittleEndian.putShort(data, 4 + offset, this.getOptions()); + LittleEndian.putInt(data, 6 + offset, this.getHorizontalPos()); + LittleEndian.putInt(data, 10 + offset, this.getVerticalPos()); LittleEndian.putInt(data, 14 + offset, this.getObjectID()); LittleEndian.putInt(data, 18 + offset, this.getDVRecNo()); return getRecordSize(); @@ -149,9 +199,11 @@ public class DVALRecord extends Record public Object clone() { DVALRecord rec = new DVALRecord(); - rec.field_unknown = this.field_unknown; + rec.field_1_options = field_1_options; + rec.field_2_horiz_pos = field_2_horiz_pos; + rec.field_3_vert_pos = field_3_vert_pos; rec.field_cbo_id = this.field_cbo_id; - rec.field_3_dv_no = this.field_3_dv_no; + rec.field_5_dv_no = this.field_5_dv_no; return rec; } -} \ No newline at end of file +} diff --git a/src/java/org/apache/poi/hssf/record/UncalcedRecord.java b/src/java/org/apache/poi/hssf/record/UncalcedRecord.java index c3243f258..a67b0b5af 100644 --- a/src/java/org/apache/poi/hssf/record/UncalcedRecord.java +++ b/src/java/org/apache/poi/hssf/record/UncalcedRecord.java @@ -55,6 +55,7 @@ public class UncalcedRecord extends Record } protected void fillFields(RecordInputStream in) { + short unused = in.readShort(); } public String toString() { diff --git a/src/java/org/apache/poi/hssf/record/formula/ErrPtg.java b/src/java/org/apache/poi/hssf/record/formula/ErrPtg.java index e382d4e75..34bad6f32 100644 --- a/src/java/org/apache/poi/hssf/record/formula/ErrPtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/ErrPtg.java @@ -29,7 +29,7 @@ import org.apache.poi.hssf.usermodel.HSSFErrorConstants; public class ErrPtg extends Ptg { public static final short sid = 0x1c; - private static final int SIZE = 7; + private static final int SIZE = 2; private byte field_1_error_code; /** Creates new ErrPtg */ diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java b/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java index f7e90d0ce..33417dad7 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java @@ -538,7 +538,13 @@ public class HSSFCell implements Cell { setCellType(CELL_TYPE_NUMERIC, false, row, col, styleIndex); } - (( NumberRecord ) record).setValue(value); + + // Save into the apropriate record + if(record instanceof FormulaRecordAggregate) { + (( FormulaRecordAggregate ) record).getFormulaRecord().setValue(value); + } else { + (( NumberRecord ) record).setValue(value); + } } /** diff --git a/src/scratchpad/src/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java b/src/scratchpad/src/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java index 2a9fc6c64..f60a6adaa 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java +++ b/src/scratchpad/src/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java @@ -217,14 +217,66 @@ public class HSSFFormulaEvaluator { /** - * If cell contains formula, it evaluates the formula, and puts the - * formula result back into the cell. - * Else if cell does not contain formula, this method leaves the cell - * unchanged. Note that the same instance of HSSFCell is returned to + * If cell contains formula, it evaluates the formula, + * and saves the result of the formula. The cell + * remains as a formula cell. + * Else if cell does not contain formula, this method leaves + * the cell unchanged. + * Note that the type of the formula result is returned, + * so you know what kind of value is also stored with + * the formula. + *
+     * int evaluatedCellType = evaluator.evaluateFormulaCell(cell);
+     * 
+ * Be aware that your cell will hold both the formula, + * and the result. If you want the cell replaced with + * the result of the formula, use {@link #evaluateInCell(HSSFCell)} + * @param cell The cell to evaluate + * @return The type of the formula result (the cell's type remains as HSSFCell.CELL_TYPE_FORMULA however) + */ + public int evaluateFormulaCell(HSSFCell cell) { + if (cell != null) { + switch (cell.getCellType()) { + case HSSFCell.CELL_TYPE_FORMULA: + CellValue cv = getCellValueForEval(internalEvaluate(cell, row, sheet, workbook)); + switch (cv.getCellType()) { + case HSSFCell.CELL_TYPE_BOOLEAN: + cell.setCellValue(cv.getBooleanValue()); + break; + case HSSFCell.CELL_TYPE_ERROR: + cell.setCellValue(cv.getErrorValue()); + break; + case HSSFCell.CELL_TYPE_NUMERIC: + cell.setCellValue(cv.getNumberValue()); + break; + case HSSFCell.CELL_TYPE_STRING: + cell.setCellValue(cv.getRichTextStringValue()); + break; + case HSSFCell.CELL_TYPE_BLANK: + break; + case HSSFCell.CELL_TYPE_FORMULA: // this will never happen, we have already evaluated the formula + break; + } + return cv.getCellType(); + } + } + return -1; + } + + /** + * If cell contains formula, it evaluates the formula, and + * puts the formula result back into the cell, in place + * of the old formula. + * Else if cell does not contain formula, this method leaves + * the cell unchanged. + * Note that the same instance of HSSFCell is returned to * allow chained calls like: *
      * int evaluatedCellType = evaluator.evaluateInCell(cell).getCellType();
      * 
+ * Be aware that your cell value will be changed to hold the + * result of the formula. If you simply want the formula + * value computed for you, use {@link #evaluateFormulaCell(HSSFCell)} * @param cell */ public HSSFCell evaluateInCell(HSSFCell cell) { diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestFormulaEvaluatorDocs.java b/src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestFormulaEvaluatorDocs.java new file mode 100644 index 000000000..cd2acc7ea --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestFormulaEvaluatorDocs.java @@ -0,0 +1,117 @@ +package org.apache.poi.hssf.usermodel; + +import java.util.Iterator; + +import junit.framework.TestCase; + +/** + * Tests to show that our documentation at + * http://poi.apache.org/hssf/eval.html + * all actually works as we'd expect them to + */ +public class TestFormulaEvaluatorDocs extends TestCase { + protected void setUp() throws Exception { + super.setUp(); + } + + /** + * http://poi.apache.org/hssf/eval.html#EvaluateAll + */ + public void testEvaluateAll() throws Exception { + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet s1 = wb.createSheet(); + HSSFSheet s2 = wb.createSheet(); + wb.setSheetName(0, "S1"); + wb.setSheetName(1, "S2"); + + HSSFRow s1r1 = s1.createRow(0); + HSSFRow s1r2 = s1.createRow(1); + HSSFRow s2r1 = s2.createRow(0); + + HSSFCell s1r1c1 = s1r1.createCell((short)0); + HSSFCell s1r1c2 = s1r1.createCell((short)1); + HSSFCell s1r1c3 = s1r1.createCell((short)2); + s1r1c1.setCellValue(22.3); + s1r1c2.setCellValue(33.4); + s1r1c3.setCellFormula("SUM(A1:B1)"); + + HSSFCell s1r2c1 = s1r2.createCell((short)0); + HSSFCell s1r2c2 = s1r2.createCell((short)1); + HSSFCell s1r2c3 = s1r2.createCell((short)2); + s1r2c1.setCellValue(-1.2); + s1r2c2.setCellValue(-3.4); + s1r2c3.setCellFormula("SUM(A2:B2)"); + + HSSFCell s2r1c1 = s2r1.createCell((short)0); + s2r1c1.setCellFormula("S1!A1"); + + // Not evaluated yet + assertEquals(0.0, s1r1c3.getNumericCellValue(), 0); + assertEquals(0.0, s1r2c3.getNumericCellValue(), 0); + assertEquals(0.0, s2r1c1.getNumericCellValue(), 0); + + // Do a full evaluate, as per our docs + // uses evaluateFormulaCell() + for(int sheetNum = 0; sheetNum < wb.getNumberOfSheets(); sheetNum++) { + HSSFSheet sheet = wb.getSheetAt(sheetNum); + HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(sheet, wb); + + for(Iterator rit = sheet.rowIterator(); rit.hasNext();) { + HSSFRow r = (HSSFRow)rit.next(); + evaluator.setCurrentRow(r); + + for(Iterator cit = r.cellIterator(); cit.hasNext();) { + HSSFCell c = (HSSFCell)cit.next(); + if(c.getCellType() == HSSFCell.CELL_TYPE_FORMULA) { + evaluator.evaluateFormulaCell(c); + + // For testing - all should be numeric + assertEquals(HSSFCell.CELL_TYPE_NUMERIC, evaluator.evaluateFormulaCell(c)); + } + } + } + } + + // Check now as expected + assertEquals(55.7, wb.getSheetAt(0).getRow(0).getCell((short)2).getNumericCellValue(), 0); + assertEquals("SUM(A1:B1)", wb.getSheetAt(0).getRow(0).getCell((short)2).getCellFormula()); + assertEquals(HSSFCell.CELL_TYPE_FORMULA, wb.getSheetAt(0).getRow(0).getCell((short)2).getCellType()); + + assertEquals(-4.6, wb.getSheetAt(0).getRow(1).getCell((short)2).getNumericCellValue(), 0); + assertEquals("SUM(A2:B2)", wb.getSheetAt(0).getRow(1).getCell((short)2).getCellFormula()); + assertEquals(HSSFCell.CELL_TYPE_FORMULA, wb.getSheetAt(0).getRow(1).getCell((short)2).getCellType()); + + assertEquals(22.3, wb.getSheetAt(1).getRow(0).getCell((short)0).getNumericCellValue(), 0); + assertEquals("S1!A1", wb.getSheetAt(1).getRow(0).getCell((short)0).getCellFormula()); + assertEquals(HSSFCell.CELL_TYPE_FORMULA, wb.getSheetAt(1).getRow(0).getCell((short)0).getCellType()); + + + // Now do the alternate call, which zaps the formulas + // uses evaluateInCell() + for(int sheetNum = 0; sheetNum < wb.getNumberOfSheets(); sheetNum++) { + HSSFSheet sheet = wb.getSheetAt(sheetNum); + HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(sheet, wb); + + for(Iterator rit = sheet.rowIterator(); rit.hasNext();) { + HSSFRow r = (HSSFRow)rit.next(); + evaluator.setCurrentRow(r); + + for(Iterator cit = r.cellIterator(); cit.hasNext();) { + HSSFCell c = (HSSFCell)cit.next(); + if(c.getCellType() == HSSFCell.CELL_TYPE_FORMULA) { + evaluator.evaluateInCell(c); + } + } + } + } + + assertEquals(55.7, wb.getSheetAt(0).getRow(0).getCell((short)2).getNumericCellValue(), 0); + assertEquals(HSSFCell.CELL_TYPE_NUMERIC, wb.getSheetAt(0).getRow(0).getCell((short)2).getCellType()); + + assertEquals(-4.6, wb.getSheetAt(0).getRow(1).getCell((short)2).getNumericCellValue(), 0); + assertEquals(HSSFCell.CELL_TYPE_NUMERIC, wb.getSheetAt(0).getRow(1).getCell((short)2).getCellType()); + + assertEquals(22.3, wb.getSheetAt(1).getRow(0).getCell((short)0).getNumericCellValue(), 0); + assertEquals(HSSFCell.CELL_TYPE_NUMERIC, wb.getSheetAt(1).getRow(0).getCell((short)0).getCellType()); + } +}