Merged revisions 614878-614909 via svnmerge from

https://svn.apache.org/repos/asf/poi/trunk

........
  r614878 | nick | 2008-01-24 15:13:05 +0100 (Thu, 24 Jan 2008) | 1 line
  
  Add another formula evaluation method, evaluateFormulaCell(cell), which will re-calculate the value for a formula, without affecting the formula itself. Add tests too, and update the documentation
........
  r614909 | nick | 2008-01-24 17:05:27 +0100 (Thu, 24 Jan 2008) | 1 line
  
  From bug #44254 - avoid some unread bytes warnings, and process the contents of DVALRecord
........


git-svn-id: https://svn.apache.org/repos/asf/poi/branches/ooxml@615185 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Ugo Cei 2008-01-25 11:39:29 +00:00
parent 66c126f70c
commit 86a0e470b9
9 changed files with 328 additions and 46 deletions

View File

@ -36,6 +36,8 @@
<!-- Don't forget to update status.xml too! -->
<release version="3.0.2-FINAL" date="2008-??-??">
<action dev="POI-DEVELOPERS" type="fix">44254 - Avoid some unread byte warnings, and properly understand DVALRecord</action>
<action dev="POI-DEVELOPERS" type="add">Add another formula evaluation method, evaluateFormulaCell(cell), which will re-calculate the value for a formula, without affecting the formula itself.</action>
<action dev="POI-DEVELOPERS" type="fix">41726 - Fix how we handle signed cell offsets in relative areas and references</action>
<action dev="POI-DEVELOPERS" type="add">44233 - Support for getting and setting a flag on the sheet, which tells excel to re-calculate all formulas on it at next reload</action>
<action dev="POI-DEVELOPERS" type="fix">44201 - Enable cloning of sheets with data validation rules</action>

View File

@ -55,10 +55,12 @@
<p>The following code demonstrates how to use the HSSFFormulaEvaluator
in the context of other POI excel reading code.
</p>
<p>There are two ways in which you can use the HSSFFormulaEvalutator API.</p>
<p>There are several ways in which you can use the HSSFFormulaEvalutator API.</p>
<anchor id="Evaluate"/>
<section><title>Using HSSFFormulaEvaluator.<strong>evaluate</strong>(HSSFCell cell)</title>
<p>This evaluates a given cell, and returns the new value,
without affecting the cell</p>
<source>
FileInputStream fis = new FileInputStream("c:/temp/test.xls");
HSSFWorkbook wb = new HSSFWorkbook(fis);
@ -102,12 +104,60 @@ switch (cellValue.getCellType()) {
</p>
</section>
<anchor id="EvaluateFormulaCell"/>
<section><title>Using HSSFFormulaEvaluator.<strong>evaluateFormulaCell</strong>(HSSFCell cell)</title>
<p><strong>evaluateFormulaCell</strong>(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</p>
<p>The return of the function is the type of the
formula result, such as HSSFCell.CELL_TYPE_BOOLEAN</p>
<source>
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 (<strong>evaluator.evaluateFormulaCell</strong>(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;
}
}
</source>
</section>
<anchor id="EvaluateInCell"/>
<section><title>Using HSSFFormulaEvaluator.<strong>evaluateInCell</strong>(HSSFCell cell)</title>
<p><strong>evaluateInCell</strong>(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.</p>
formula is evaluated, and the new value saved into the cell,
in place of the old formula.</p>
<source>
FileInputStream fis = new FileInputStream("/somepath/test.xls");
HSSFWorkbook wb = new HSSFWorkbook(fis);
@ -154,14 +204,14 @@ for(int sheetNum = 0; sheetNum &lt; 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);
}
}
}

View File

@ -33,6 +33,8 @@
<!-- Don't forget to update changes.xml too! -->
<changes>
<release version="3.0.2-FINAL" date="2008-??-??">
<action dev="POI-DEVELOPERS" type="fix">44254 - Avoid some unread byte warnings, and properly understand DVALRecord</action>
<action dev="POI-DEVELOPERS" type="add">Add another formula evaluation method, evaluateFormulaCell(cell), which will re-calculate the value for a formula, without affecting the formula itself.</action>
<action dev="POI-DEVELOPERS" type="fix">41726 - Fix how we handle signed cell offsets in relative areas and references</action>
<action dev="POI-DEVELOPERS" type="add">44233 - Support for getting and setting a flag on the sheet, which tells excel to re-calculate all formulas on it at next reload</action>
<action dev="POI-DEVELOPERS" type="fix">44201 - Enable cloning of sheets with data validation rules</action>

View File

@ -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;
}
}
}

View File

@ -55,6 +55,7 @@ public class UncalcedRecord extends Record
}
protected void fillFields(RecordInputStream in) {
short unused = in.readShort();
}
public String toString() {

View File

@ -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 */

View File

@ -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);
}
}
/**

View File

@ -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.
* <pre>
* int evaluatedCellType = evaluator.evaluateFormulaCell(cell);
* </pre>
* 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:
* <pre>
* int evaluatedCellType = evaluator.evaluateInCell(cell).getCellType();
* </pre>
* 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) {

View File

@ -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());
}
}