Extended support for cached results of formula cells

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@694631 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Josh Micich 2008-09-12 07:43:20 +00:00
parent 9b9d63275a
commit 21fa41ec23
14 changed files with 993 additions and 757 deletions

View File

@ -37,6 +37,7 @@
<!-- Don't forget to update status.xml too! --> <!-- Don't forget to update status.xml too! -->
<release version="3.1.1-alpha1" date="2008-??-??"> <release version="3.1.1-alpha1" date="2008-??-??">
<action dev="POI-DEVELOPERS" type="add">Extended support for cached results of formula cells</action>
<action dev="POI-DEVELOPERS" type="fix">45639 - Fixed AIOOBE due to bad index logic in ColumnInfoRecordsAggregate</action> <action dev="POI-DEVELOPERS" type="fix">45639 - Fixed AIOOBE due to bad index logic in ColumnInfoRecordsAggregate</action>
<action dev="POI-DEVELOPERS" type="fix">Fixed special cases of INDEX function (single column/single row, errors)</action> <action dev="POI-DEVELOPERS" type="fix">Fixed special cases of INDEX function (single column/single row, errors)</action>
<action dev="POI-DEVELOPERS" type="add">45761 - Support for Very Hidden excel sheets in HSSF</action> <action dev="POI-DEVELOPERS" type="add">45761 - Support for Very Hidden excel sheets in HSSF</action>

View File

@ -34,6 +34,7 @@
<!-- Don't forget to update changes.xml too! --> <!-- Don't forget to update changes.xml too! -->
<changes> <changes>
<release version="3.1.1-alpha1" date="2008-??-??"> <release version="3.1.1-alpha1" date="2008-??-??">
<action dev="POI-DEVELOPERS" type="add">Extended support for cached results of formula cells</action>
<action dev="POI-DEVELOPERS" type="fix">45639 - Fixed AIOOBE due to bad index logic in ColumnInfoRecordsAggregate</action> <action dev="POI-DEVELOPERS" type="fix">45639 - Fixed AIOOBE due to bad index logic in ColumnInfoRecordsAggregate</action>
<action dev="POI-DEVELOPERS" type="fix">Fixed special cases of INDEX function (single column/single row, errors)</action> <action dev="POI-DEVELOPERS" type="fix">Fixed special cases of INDEX function (single column/single row, errors)</action>
<action dev="POI-DEVELOPERS" type="add">45761 - Support for Very Hidden excel sheets in HSSF</action> <action dev="POI-DEVELOPERS" type="add">45761 - Support for Very Hidden excel sheets in HSSF</action>

View File

@ -14,6 +14,7 @@
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
==================================================================== */ ==================================================================== */
package org.apache.poi.hssf.extractor; package org.apache.poi.hssf.extractor;
import java.io.IOException; import java.io.IOException;
@ -49,10 +50,10 @@ import org.apache.poi.poifs.filesystem.POIFSFileSystem;
/** /**
* A text extractor for Excel files, that is based * A text extractor for Excel files, that is based
* on the hssf eventusermodel api. * on the hssf eventusermodel api.
* It will typically use less memory than * It will typically use less memory than
* {@link ExcelExtractor}, but may not provide * {@link ExcelExtractor}, but may not provide
* the same richness of formatting. * the same richness of formatting.
* Returns the textual content of the file, suitable for * Returns the textual content of the file, suitable for
* indexing by something like Lucene, but not really * indexing by something like Lucene, but not really
* intended for display to the user. * intended for display to the user.
* To turn an excel file into a CSV or similar, then see * To turn an excel file into a CSV or similar, then see
@ -63,8 +64,8 @@ public class EventBasedExcelExtractor extends POIOLE2TextExtractor {
private POIFSFileSystem fs; private POIFSFileSystem fs;
private boolean includeSheetNames = true; private boolean includeSheetNames = true;
private boolean formulasNotResults = false; private boolean formulasNotResults = false;
public EventBasedExcelExtractor(POIFSFileSystem fs) throws IOException { public EventBasedExcelExtractor(POIFSFileSystem fs) {
super(null); super(null);
this.fs = fs; this.fs = fs;
} }
@ -98,8 +99,8 @@ public class EventBasedExcelExtractor extends POIOLE2TextExtractor {
public void setFormulasNotResults(boolean formulasNotResults) { public void setFormulasNotResults(boolean formulasNotResults) {
this.formulasNotResults = formulasNotResults; this.formulasNotResults = formulasNotResults;
} }
/** /**
* Retreives the text contents of the file * Retreives the text contents of the file
*/ */
@ -107,7 +108,7 @@ public class EventBasedExcelExtractor extends POIOLE2TextExtractor {
String text = null; String text = null;
try { try {
TextListener tl = triggerExtraction(); TextListener tl = triggerExtraction();
text = tl.text.toString(); text = tl.text.toString();
if(! text.endsWith("\n")) { if(! text.endsWith("\n")) {
text = text + "\n"; text = text + "\n";
@ -115,37 +116,37 @@ public class EventBasedExcelExtractor extends POIOLE2TextExtractor {
} catch(IOException e) { } catch(IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
return text; return text;
} }
private TextListener triggerExtraction() throws IOException { private TextListener triggerExtraction() throws IOException {
TextListener tl = new TextListener(); TextListener tl = new TextListener();
FormatTrackingHSSFListener ft = new FormatTrackingHSSFListener(tl); FormatTrackingHSSFListener ft = new FormatTrackingHSSFListener(tl);
tl.ft = ft; tl.ft = ft;
// Register and process // Register and process
HSSFEventFactory factory = new HSSFEventFactory(); HSSFEventFactory factory = new HSSFEventFactory();
HSSFRequest request = new HSSFRequest(); HSSFRequest request = new HSSFRequest();
request.addListenerForAllRecords(ft); request.addListenerForAllRecords(ft);
factory.processWorkbookEvents(request, fs); factory.processWorkbookEvents(request, fs);
return tl; return tl;
} }
private class TextListener implements HSSFListener { private class TextListener implements HSSFListener {
private FormatTrackingHSSFListener ft; private FormatTrackingHSSFListener ft;
private SSTRecord sstRecord; private SSTRecord sstRecord;
private List sheetNames = new ArrayList(); private List sheetNames = new ArrayList();
private StringBuffer text = new StringBuffer(); private StringBuffer text = new StringBuffer();
private int sheetNum = -1; private int sheetNum = -1;
private int rowNum; private int rowNum;
private boolean outputNextStringValue = false; private boolean outputNextStringValue = false;
private int nextRow = -1; private int nextRow = -1;
public void processRecord(Record record) { public void processRecord(Record record) {
String thisText = null; String thisText = null;
int thisRow = -1; int thisRow = -1;
@ -160,7 +161,7 @@ public class EventBasedExcelExtractor extends POIOLE2TextExtractor {
if(bof.getType() == BOFRecord.TYPE_WORKSHEET) { if(bof.getType() == BOFRecord.TYPE_WORKSHEET) {
sheetNum++; sheetNum++;
rowNum = -1; rowNum = -1;
if(includeSheetNames) { if(includeSheetNames) {
if(text.length() > 0) text.append("\n"); if(text.length() > 0) text.append("\n");
text.append(sheetNames.get(sheetNum)); text.append(sheetNames.get(sheetNum));
@ -170,60 +171,60 @@ public class EventBasedExcelExtractor extends POIOLE2TextExtractor {
case SSTRecord.sid: case SSTRecord.sid:
sstRecord = (SSTRecord)record; sstRecord = (SSTRecord)record;
break; break;
case FormulaRecord.sid: case FormulaRecord.sid:
FormulaRecord frec = (FormulaRecord) record; FormulaRecord frec = (FormulaRecord) record;
thisRow = frec.getRow(); thisRow = frec.getRow();
if(formulasNotResults) { if(formulasNotResults) {
thisText = FormulaParser.toFormulaString(null, frec.getParsedExpression()); thisText = FormulaParser.toFormulaString(null, frec.getParsedExpression());
} else { } else {
if(Double.isNaN( frec.getValue() )) { if(frec.hasCachedResultString()) {
// Formula result is a string // Formula result is a string
// This is stored in the next record // This is stored in the next record
outputNextStringValue = true; outputNextStringValue = true;
nextRow = frec.getRow(); nextRow = frec.getRow();
} else { } else {
thisText = formatNumberDateCell(frec, frec.getValue()); thisText = formatNumberDateCell(frec, frec.getValue());
} }
} }
break; break;
case StringRecord.sid: case StringRecord.sid:
if(outputNextStringValue) { if(outputNextStringValue) {
// String for formula // String for formula
StringRecord srec = (StringRecord)record; StringRecord srec = (StringRecord)record;
thisText = srec.getString(); thisText = srec.getString();
thisRow = nextRow; thisRow = nextRow;
outputNextStringValue = false; outputNextStringValue = false;
} }
break; break;
case LabelRecord.sid: case LabelRecord.sid:
LabelRecord lrec = (LabelRecord) record; LabelRecord lrec = (LabelRecord) record;
thisRow = lrec.getRow(); thisRow = lrec.getRow();
thisText = lrec.getValue(); thisText = lrec.getValue();
break; break;
case LabelSSTRecord.sid: case LabelSSTRecord.sid:
LabelSSTRecord lsrec = (LabelSSTRecord) record; LabelSSTRecord lsrec = (LabelSSTRecord) record;
thisRow = lsrec.getRow(); thisRow = lsrec.getRow();
if(sstRecord == null) { if(sstRecord == null) {
throw new IllegalStateException("No SST record found"); throw new IllegalStateException("No SST record found");
} }
thisText = sstRecord.getString(lsrec.getSSTIndex()).toString(); thisText = sstRecord.getString(lsrec.getSSTIndex()).toString();
break; break;
case NoteRecord.sid: case NoteRecord.sid:
NoteRecord nrec = (NoteRecord) record; NoteRecord nrec = (NoteRecord) record;
thisRow = nrec.getRow(); thisRow = nrec.getRow();
// TODO: Find object to match nrec.getShapeId() // TODO: Find object to match nrec.getShapeId()
break; break;
case NumberRecord.sid: case NumberRecord.sid:
NumberRecord numrec = (NumberRecord) record; NumberRecord numrec = (NumberRecord) record;
thisRow = numrec.getRow(); thisRow = numrec.getRow();
thisText = formatNumberDateCell(numrec, numrec.getValue()); thisText = formatNumberDateCell(numrec, numrec.getValue());
break; break;
default: default:
break; break;
} }
if(thisText != null) { if(thisText != null) {
if(thisRow != rowNum) { if(thisRow != rowNum) {
rowNum = thisRow; rowNum = thisRow;
@ -235,42 +236,42 @@ public class EventBasedExcelExtractor extends POIOLE2TextExtractor {
text.append(thisText); text.append(thisText);
} }
} }
/** /**
* Formats a number or date cell, be that a real number, or the * Formats a number or date cell, be that a real number, or the
* answer to a formula * answer to a formula
*/ */
private String formatNumberDateCell(CellValueRecordInterface cell, double value) { private String formatNumberDateCell(CellValueRecordInterface cell, double value) {
// Get the built in format, if there is one // Get the built in format, if there is one
int formatIndex = ft.getFormatIndex(cell); int formatIndex = ft.getFormatIndex(cell);
String formatString = ft.getFormatString(cell); String formatString = ft.getFormatString(cell);
if(formatString == null) { if(formatString == null) {
return Double.toString(value); return Double.toString(value);
} else { } else {
// Is it a date? // Is it a date?
if(HSSFDateUtil.isADateFormat(formatIndex,formatString) && if(HSSFDateUtil.isADateFormat(formatIndex,formatString) &&
HSSFDateUtil.isValidExcelDate(value)) { HSSFDateUtil.isValidExcelDate(value)) {
// Java wants M not m for month // Java wants M not m for month
formatString = formatString.replace('m','M'); formatString = formatString.replace('m','M');
// Change \- into -, if it's there // Change \- into -, if it's there
formatString = formatString.replaceAll("\\\\-","-"); formatString = formatString.replaceAll("\\\\-","-");
// Format as a date // Format as a date
Date d = HSSFDateUtil.getJavaDate(value, false); Date d = HSSFDateUtil.getJavaDate(value, false);
DateFormat df = new SimpleDateFormat(formatString); DateFormat df = new SimpleDateFormat(formatString);
return df.format(d); return df.format(d);
} else { } else {
if(formatString == "General") { if(formatString == "General") {
// Some sort of wierd default // Some sort of wierd default
return Double.toString(value); return Double.toString(value);
} }
// Format as a number // Format as a number
DecimalFormat df = new DecimalFormat(formatString); DecimalFormat df = new DecimalFormat(formatString);
return df.format(value); return df.format(value);
} }
} }
} }
} }
} }

View File

@ -14,16 +14,16 @@
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
==================================================================== */ ==================================================================== */
package org.apache.poi.hssf.extractor; package org.apache.poi.hssf.extractor;
import java.io.IOException; import java.io.IOException;
import org.apache.poi.POIOLE2TextExtractor; import org.apache.poi.POIOLE2TextExtractor;
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.usermodel.HeaderFooter; import org.apache.poi.hssf.usermodel.HeaderFooter;
import org.apache.poi.hssf.usermodel.HSSFCell; import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFComment; import org.apache.poi.hssf.usermodel.HSSFComment;
import org.apache.poi.hssf.usermodel.HSSFFooter;
import org.apache.poi.hssf.usermodel.HSSFHeader;
import org.apache.poi.hssf.usermodel.HSSFRichTextString; import org.apache.poi.hssf.usermodel.HSSFRichTextString;
import org.apache.poi.hssf.usermodel.HSSFRow; import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFSheet;
@ -110,40 +110,52 @@ public class ExcelExtractor extends POIOLE2TextExtractor {
int lastCell = row.getLastCellNum(); int lastCell = row.getLastCellNum();
for(int k=firstCell;k<lastCell;k++) { for(int k=firstCell;k<lastCell;k++) {
HSSFCell cell = row.getCell(k); HSSFCell cell = row.getCell(k);
boolean outputContents = false;
if(cell == null) { continue; } if(cell == null) { continue; }
boolean outputContents = true;
switch(cell.getCellType()) { switch(cell.getCellType()) {
case HSSFCell.CELL_TYPE_BLANK:
outputContents = false;
break;
case HSSFCell.CELL_TYPE_STRING: case HSSFCell.CELL_TYPE_STRING:
text.append(cell.getRichStringCellValue().getString()); text.append(cell.getRichStringCellValue().getString());
outputContents = true;
break; break;
case HSSFCell.CELL_TYPE_NUMERIC: case HSSFCell.CELL_TYPE_NUMERIC:
// Note - we don't apply any formatting! // Note - we don't apply any formatting!
text.append(cell.getNumericCellValue()); text.append(cell.getNumericCellValue());
outputContents = true;
break; break;
case HSSFCell.CELL_TYPE_BOOLEAN: case HSSFCell.CELL_TYPE_BOOLEAN:
text.append(cell.getBooleanCellValue()); text.append(cell.getBooleanCellValue());
outputContents = true; break;
case HSSFCell.CELL_TYPE_ERROR:
text.append(ErrorEval.getText(cell.getErrorCellValue()));
break; break;
case HSSFCell.CELL_TYPE_FORMULA: case HSSFCell.CELL_TYPE_FORMULA:
if(formulasNotResults) { if(formulasNotResults) {
text.append(cell.getCellFormula()); text.append(cell.getCellFormula());
} else { } else {
// Try it as a string, if not as a number switch(cell.getCachedFormulaResultType()) {
HSSFRichTextString str = case HSSFCell.CELL_TYPE_STRING:
cell.getRichStringCellValue(); HSSFRichTextString str = cell.getRichStringCellValue();
if(str != null && str.length() > 0) { if(str != null && str.length() > 0) {
text.append(str.toString()); text.append(str.toString());
} else { }
// Try and treat it as a number break;
double val = cell.getNumericCellValue(); case HSSFCell.CELL_TYPE_NUMERIC:
text.append(val); text.append(cell.getNumericCellValue());
break;
case HSSFCell.CELL_TYPE_BOOLEAN:
text.append(cell.getBooleanCellValue());
break;
case HSSFCell.CELL_TYPE_ERROR:
text.append(ErrorEval.getText(cell.getErrorCellValue()));
break;
} }
} }
outputContents = true;
break; break;
default:
throw new RuntimeException("Unexpected cell type (" + cell.getCellType() + ")");
} }
// Output the comment, if requested and exists // Output the comment, if requested and exists

View File

@ -18,6 +18,8 @@
package org.apache.poi.hssf.record; package org.apache.poi.hssf.record;
import org.apache.poi.hssf.record.formula.Ptg; import org.apache.poi.hssf.record.formula.Ptg;
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.util.BitField; import org.apache.poi.util.BitField;
import org.apache.poi.util.BitFieldFactory; import org.apache.poi.util.BitFieldFactory;
import org.apache.poi.util.HexDump; import org.apache.poi.util.HexDump;
@ -32,264 +34,430 @@ import org.apache.poi.util.LittleEndian;
*/ */
public final class FormulaRecord extends Record implements CellValueRecordInterface { public final class FormulaRecord extends Record implements CellValueRecordInterface {
public static final short sid = 0x0006; // docs say 406...because of a bug Microsoft support site article #Q184647) public static final short sid = 0x0006; // docs say 406...because of a bug Microsoft support site article #Q184647)
private static int FIXED_SIZE = 22; private static int FIXED_SIZE = 22;
private static final BitField alwaysCalc = BitFieldFactory.getInstance(0x0001); private static final BitField alwaysCalc = BitFieldFactory.getInstance(0x0001);
private static final BitField calcOnLoad = BitFieldFactory.getInstance(0x0002); private static final BitField calcOnLoad = BitFieldFactory.getInstance(0x0002);
private static final BitField sharedFormula = BitFieldFactory.getInstance(0x0008); private static final BitField sharedFormula = BitFieldFactory.getInstance(0x0008);
private int field_1_row; /**
private short field_2_column; * Manages the cached formula result values of other types besides numeric.
private short field_3_xf; * Excel encodes the same 8 bytes that would be field_4_value with various NaN
private double field_4_value; * values that are decoded/encoded by this class.
private short field_5_options; */
private int field_6_zero; private static final class SpecialCachedValue {
private Ptg[] field_8_parsed_expr; /** deliberately chosen by Excel in order to encode other values within Double NaNs */
private static final long BIT_MARKER = 0xFFFF000000000000L;
private static final int VARIABLE_DATA_LENGTH = 6;
private static final int DATA_INDEX = 2;
/** public static final int STRING = 0;
* Since the NaN support seems sketchy (different constants) we'll store and spit it out directly public static final int BOOLEAN = 1;
*/ public static final int ERROR_CODE = 2;
private byte[] value_data; public static final int EMPTY = 3;
/** Creates new FormulaRecord */ private final byte[] _variableData;
public FormulaRecord() { private SpecialCachedValue(byte[] data) {
field_8_parsed_expr = Ptg.EMPTY_PTG_ARRAY; _variableData = data;
} }
public int getTypeCode() {
return _variableData[0];
}
/** /**
* Constructs a Formula record and sets its fields appropriately. * @return <code>null</code> if the double value encoded by <tt>valueLongBits</tt>
* Note - id must be 0x06 (NOT 0x406 see MSKB #Q184647 for an * is a normal (non NaN) double value.
* "explanation of this bug in the documentation) or an exception */
* will be throw upon validation public static SpecialCachedValue create(long valueLongBits) {
* if ((BIT_MARKER & valueLongBits) != BIT_MARKER) {
* @param in the RecordInputstream to read the record from return null;
*/ }
public FormulaRecord(RecordInputStream in) { byte[] result = new byte[VARIABLE_DATA_LENGTH];
super(in); long x = valueLongBits;
} for (int i=0; i<VARIABLE_DATA_LENGTH; i++) {
result[i] = (byte) x;
x >>= 8;
}
switch (result[0]) {
case STRING:
case BOOLEAN:
case ERROR_CODE:
case EMPTY:
break;
default:
throw new RecordFormatException("Bad special value code (" + result[0] + ")");
}
return new SpecialCachedValue(result);
}
public void serialize(byte[] data, int offset) {
System.arraycopy(_variableData, 0, data, offset, VARIABLE_DATA_LENGTH);
LittleEndian.putUShort(data, offset+VARIABLE_DATA_LENGTH, 0xFFFF);
}
public String formatDebugString() {
return formatValue() + ' ' + HexDump.toHex(_variableData);
}
private String formatValue() {
int typeCode = getTypeCode();
switch (typeCode) {
case STRING: return "<string>";
case BOOLEAN: return getDataValue() == 0 ? "FALSE" : "TRUE";
case ERROR_CODE: return ErrorEval.getText(getDataValue());
case EMPTY: return "<empty>";
}
return "#error(type=" + typeCode + ")#";
}
private int getDataValue() {
return _variableData[DATA_INDEX];
}
public static SpecialCachedValue createCachedEmptyValue() {
return create(EMPTY, 0);
}
public static SpecialCachedValue createForString() {
return create(STRING, 0);
}
public static SpecialCachedValue createCachedBoolean(boolean b) {
return create(BOOLEAN, b ? 0 : 1);
}
public static SpecialCachedValue createCachedErrorCode(int errorCode) {
return create(ERROR_CODE, errorCode);
}
private static SpecialCachedValue create(int code, int data) {
byte[] vd = {
(byte) code,
0,
(byte) data,
0,
0,
0,
};
return new SpecialCachedValue(vd);
}
public String toString() {
StringBuffer sb = new StringBuffer(64);
sb.append(getClass().getName());
sb.append('[').append(formatValue()).append(']');
return sb.toString();
}
public int getValueType() {
int typeCode = getTypeCode();
switch (typeCode) {
case STRING: return HSSFCell.CELL_TYPE_STRING;
case BOOLEAN: return HSSFCell.CELL_TYPE_BOOLEAN;
case ERROR_CODE: return HSSFCell.CELL_TYPE_ERROR;
case EMPTY: return HSSFCell.CELL_TYPE_STRING; // is this correct?
}
throw new IllegalStateException("Unexpected type id (" + typeCode + ")");
}
public boolean getBooleanValue() {
if (getTypeCode() != BOOLEAN) {
throw new IllegalStateException("Not a boolean cached value - " + formatValue());
}
return getDataValue() != 0;
}
public int getErrorValue() {
if (getTypeCode() != ERROR_CODE) {
throw new IllegalStateException("Not an error cached value - " + formatValue());
}
return getDataValue();
}
}
protected void fillFields(RecordInputStream in) {
field_1_row = in.readUShort();
field_2_column = in.readShort();
field_3_xf = in.readShort();
field_4_value = in.readDouble();
field_5_options = in.readShort();
if (Double.isNaN(field_4_value)) {
value_data = in.getNANData();
}
field_6_zero = in.readInt(); private int field_1_row;
int field_7_expression_len = in.readShort(); // this length does not include any extra array data private short field_2_column;
field_8_parsed_expr = Ptg.readTokens(field_7_expression_len, in); private short field_3_xf;
if (in.remaining() == 10) { private double field_4_value;
// TODO - this seems to occur when IntersectionPtg is present private short field_5_options;
// 10 extra bytes are just 0x01 and 0x00 private int field_6_zero;
// This causes POI stderr: "WARN. Unread 10 bytes of record 0x6" private Ptg[] field_8_parsed_expr;
}
}
public void setRow(int row) { /**
field_1_row = row; * Since the NaN support seems sketchy (different constants) we'll store and spit it out directly
} */
private SpecialCachedValue specialCachedValue;
public void setColumn(short column) { /** Creates new FormulaRecord */
field_2_column = column;
}
public void setXFIndex(short xf) { public FormulaRecord() {
field_3_xf = xf; field_8_parsed_expr = Ptg.EMPTY_PTG_ARRAY;
} }
/** /**
* set the calculated value of the formula * Constructs a Formula record and sets its fields appropriately.
* * Note - id must be 0x06 (NOT 0x406 see MSKB #Q184647 for an
* @param value calculated value * "explanation of this bug in the documentation) or an exception
*/ * will be throw upon validation
public void setValue(double value) { *
field_4_value = value; * @param in the RecordInputstream to read the record from
} */
/** public FormulaRecord(RecordInputStream in) {
* set the option flags super(in);
* }
* @param options bitmask
*/
public void setOptions(short options) {
field_5_options = options;
}
public int getRow() { protected void fillFields(RecordInputStream in) {
return field_1_row; field_1_row = in.readUShort();
} field_2_column = in.readShort();
field_3_xf = in.readShort();
long valueLongBits = in.readLong();
field_5_options = in.readShort();
specialCachedValue = SpecialCachedValue.create(valueLongBits);
if (specialCachedValue == null) {
field_4_value = Double.longBitsToDouble(valueLongBits);
}
public short getColumn() { field_6_zero = in.readInt();
return field_2_column; int field_7_expression_len = in.readShort(); // this length does not include any extra array data
} field_8_parsed_expr = Ptg.readTokens(field_7_expression_len, in);
if (in.remaining() == 10) {
// TODO - this seems to occur when IntersectionPtg is present
// 10 extra bytes are just 0x01 and 0x00
// This causes POI stderr: "WARN. Unread 10 bytes of record 0x6"
}
}
public short getXFIndex() {
return field_3_xf;
}
/** public void setRow(int row) {
* get the calculated value of the formula field_1_row = row;
* }
* @return calculated value
*/
public double getValue() {
return field_4_value;
}
/** public void setColumn(short column) {
* get the option flags field_2_column = column;
* }
* @return bitmask
*/
public short getOptions() {
return field_5_options;
}
public boolean isSharedFormula() { public void setXFIndex(short xf) {
return sharedFormula.isSet(field_5_options); field_3_xf = xf;
} }
public void setSharedFormula(boolean flag) {
field_5_options =
sharedFormula.setShortBoolean(field_5_options, flag);
}
public boolean isAlwaysCalc() { /**
return alwaysCalc.isSet(field_5_options); * set the calculated value of the formula
} *
public void setAlwaysCalc(boolean flag) { * @param value calculated value
field_5_options = */
alwaysCalc.setShortBoolean(field_5_options, flag); public void setValue(double value) {
} field_4_value = value;
specialCachedValue = null;
}
public boolean isCalcOnLoad() { public void setCachedResultTypeEmptyString() {
return calcOnLoad.isSet(field_5_options); specialCachedValue = SpecialCachedValue.createCachedEmptyValue();
} }
public void setCalcOnLoad(boolean flag) { public void setCachedResultTypeString() {
field_5_options = specialCachedValue = SpecialCachedValue.createForString();
calcOnLoad.setShortBoolean(field_5_options, flag); }
} public void setCachedResultErrorCode(int errorCode) {
specialCachedValue = SpecialCachedValue.createCachedErrorCode(errorCode);
}
public void setCachedResultBoolean(boolean value) {
specialCachedValue = SpecialCachedValue.createCachedBoolean(value);
}
/**
* @return <code>true</code> if this {@link FormulaRecord} is followed by a
* {@link StringRecord} representing the cached text result of the formula
* evaluation.
*/
public boolean hasCachedResultString() {
if (specialCachedValue == null) {
return false;
}
return specialCachedValue.getTypeCode() == SpecialCachedValue.STRING;
}
/** public int getCachedResultType() {
* @return the formula tokens. never <code>null</code> if (specialCachedValue == null) {
*/ return HSSFCell.CELL_TYPE_NUMERIC;
public Ptg[] getParsedExpression() { }
return (Ptg[]) field_8_parsed_expr.clone(); return specialCachedValue.getValueType();
} }
public void setParsedExpression(Ptg[] ptgs) { public boolean getCachedBooleanValue() {
field_8_parsed_expr = ptgs; return specialCachedValue.getBooleanValue();
} }
public int getCachedErrorValue() {
return specialCachedValue.getErrorValue();
}
/**
* called by constructor, should throw runtime exception in the event of a
* record passed with a differing ID.
*
* @param id alleged id for this record
*/
protected void validateSid(short id) {
if (id != sid) {
throw new RecordFormatException("NOT A FORMULA RECORD");
}
}
public short getSid() { /**
return sid; * set the option flags
} *
* @param options bitmask
*/
public void setOptions(short options) {
field_5_options = options;
}
private int getDataSize() { public int getRow() {
return FIXED_SIZE + Ptg.getEncodedSize(field_8_parsed_expr); return field_1_row;
} }
public int serialize(int offset, byte [] data) {
int dataSize = getDataSize(); public short getColumn() {
return field_2_column;
}
LittleEndian.putShort(data, 0 + offset, sid); public short getXFIndex() {
LittleEndian.putUShort(data, 2 + offset, dataSize); return field_3_xf;
LittleEndian.putUShort(data, 4 + offset, getRow()); }
LittleEndian.putShort(data, 6 + offset, getColumn());
LittleEndian.putShort(data, 8 + offset, getXFIndex());
//only reserialize if the value is still NaN and we have old nan data /**
if (Double.isNaN(getValue()) && value_data != null) { * get the calculated value of the formula
System.arraycopy(value_data,0,data,10 + offset,value_data.length); *
} else { * @return calculated value
LittleEndian.putDouble(data, 10 + offset, field_4_value); */
} public double getValue() {
return field_4_value;
}
LittleEndian.putShort(data, 18 + offset, getOptions()); /**
* get the option flags
*
* @return bitmask
*/
public short getOptions() {
return field_5_options;
}
//when writing the chn field (offset 20), it's supposed to be 0 but ignored on read public boolean isSharedFormula() {
//Microsoft Excel Developer's Kit Page 318 return sharedFormula.isSet(field_5_options);
LittleEndian.putInt(data, 20 + offset, 0); }
int formulaTokensSize = Ptg.getEncodedSizeWithoutArrayData(field_8_parsed_expr); public void setSharedFormula(boolean flag) {
LittleEndian.putUShort(data, 24 + offset, formulaTokensSize); field_5_options =
Ptg.serializePtgs(field_8_parsed_expr, data, 26+offset); sharedFormula.setShortBoolean(field_5_options, flag);
return 4 + dataSize; }
}
public int getRecordSize() { public boolean isAlwaysCalc() {
return 4 + getDataSize(); return alwaysCalc.isSet(field_5_options);
} }
public void setAlwaysCalc(boolean flag) {
field_5_options =
alwaysCalc.setShortBoolean(field_5_options, flag);
}
public boolean isInValueSection() { public boolean isCalcOnLoad() {
return true; return calcOnLoad.isSet(field_5_options);
} }
public void setCalcOnLoad(boolean flag) {
field_5_options =
calcOnLoad.setShortBoolean(field_5_options, flag);
}
public boolean isValue() { /**
return true; * @return the formula tokens. never <code>null</code>
} */
public Ptg[] getParsedExpression() {
return (Ptg[]) field_8_parsed_expr.clone();
}
public String toString() { public void setParsedExpression(Ptg[] ptgs) {
field_8_parsed_expr = ptgs;
}
StringBuffer sb = new StringBuffer(); /**
sb.append("[FORMULA]\n"); * called by constructor, should throw runtime exception in the event of a
sb.append(" .row = ").append(HexDump.shortToHex(getRow())).append("\n"); * record passed with a differing ID.
sb.append(" .column = ").append(HexDump.shortToHex(getColumn())).append("\n"); *
sb.append(" .xf = ").append(HexDump.shortToHex(getXFIndex())).append("\n"); * @param id alleged id for this record
sb.append(" .value = "); */
if (Double.isNaN(this.getValue()) && value_data != null) { protected void validateSid(short id) {
sb.append("(NaN)").append(HexDump.dump(value_data,0,0)).append("\n"); if (id != sid) {
} else { throw new RecordFormatException("NOT A FORMULA RECORD");
sb.append(getValue()).append("\n"); }
} }
sb.append(" .options = ").append(HexDump.shortToHex(getOptions())).append("\n");
sb.append(" .alwaysCalc= ").append(alwaysCalc.isSet(getOptions())).append("\n");
sb.append(" .calcOnLoad= ").append(calcOnLoad.isSet(getOptions())).append("\n");
sb.append(" .shared = ").append(sharedFormula.isSet(getOptions())).append("\n");
sb.append(" .zero = ").append(HexDump.intToHex(field_6_zero)).append("\n");
for (int k = 0; k < field_8_parsed_expr.length; k++ ) { public short getSid() {
sb.append(" Ptg[").append(k).append("]="); return sid;
Ptg ptg = field_8_parsed_expr[k]; }
sb.append(ptg.toString()).append(ptg.getRVAType()).append("\n");
}
sb.append("[/FORMULA]\n");
return sb.toString();
}
public Object clone() { private int getDataSize() {
FormulaRecord rec = new FormulaRecord(); return FIXED_SIZE + Ptg.getEncodedSize(field_8_parsed_expr);
rec.field_1_row = field_1_row; }
rec.field_2_column = field_2_column; public int serialize(int offset, byte [] data) {
rec.field_3_xf = field_3_xf;
rec.field_4_value = field_4_value; int dataSize = getDataSize();
rec.field_5_options = field_5_options;
rec.field_6_zero = field_6_zero; LittleEndian.putShort(data, 0 + offset, sid);
int nTokens = field_8_parsed_expr.length; LittleEndian.putUShort(data, 2 + offset, dataSize);
Ptg[] ptgs = new Ptg[nTokens]; LittleEndian.putUShort(data, 4 + offset, getRow());
for (int i=0; i< nTokens; i++) { LittleEndian.putShort(data, 6 + offset, getColumn());
ptgs[i] = field_8_parsed_expr[i].copy(); LittleEndian.putShort(data, 8 + offset, getXFIndex());
}
rec.field_8_parsed_expr = ptgs; if (specialCachedValue == null) {
rec.value_data = value_data; LittleEndian.putDouble(data, 10 + offset, field_4_value);
return rec; } else {
} specialCachedValue.serialize(data, 10+offset);
}
LittleEndian.putShort(data, 18 + offset, getOptions());
//when writing the chn field (offset 20), it's supposed to be 0 but ignored on read
//Microsoft Excel Developer's Kit Page 318
LittleEndian.putInt(data, 20 + offset, 0);
int formulaTokensSize = Ptg.getEncodedSizeWithoutArrayData(field_8_parsed_expr);
LittleEndian.putUShort(data, 24 + offset, formulaTokensSize);
Ptg.serializePtgs(field_8_parsed_expr, data, 26+offset);
return 4 + dataSize;
}
public int getRecordSize() {
return 4 + getDataSize();
}
public boolean isInValueSection() {
return true;
}
public boolean isValue() {
return true;
}
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("[FORMULA]\n");
sb.append(" .row = ").append(HexDump.shortToHex(getRow())).append("\n");
sb.append(" .column = ").append(HexDump.shortToHex(getColumn())).append("\n");
sb.append(" .xf = ").append(HexDump.shortToHex(getXFIndex())).append("\n");
sb.append(" .value = ");
if (specialCachedValue == null) {
sb.append(field_4_value).append("\n");
} else {
sb.append(specialCachedValue.formatDebugString()).append("\n");
}
sb.append(" .options = ").append(HexDump.shortToHex(getOptions())).append("\n");
sb.append(" .alwaysCalc= ").append(alwaysCalc.isSet(getOptions())).append("\n");
sb.append(" .calcOnLoad= ").append(calcOnLoad.isSet(getOptions())).append("\n");
sb.append(" .shared = ").append(sharedFormula.isSet(getOptions())).append("\n");
sb.append(" .zero = ").append(HexDump.intToHex(field_6_zero)).append("\n");
for (int k = 0; k < field_8_parsed_expr.length; k++ ) {
sb.append(" Ptg[").append(k).append("]=");
Ptg ptg = field_8_parsed_expr[k];
sb.append(ptg.toString()).append(ptg.getRVAType()).append("\n");
}
sb.append("[/FORMULA]\n");
return sb.toString();
}
public Object clone() {
FormulaRecord rec = new FormulaRecord();
rec.field_1_row = field_1_row;
rec.field_2_column = field_2_column;
rec.field_3_xf = field_3_xf;
rec.field_4_value = field_4_value;
rec.field_5_options = field_5_options;
rec.field_6_zero = field_6_zero;
int nTokens = field_8_parsed_expr.length;
Ptg[] ptgs = new Ptg[nTokens];
for (int i = 0; i < nTokens; i++) {
ptgs[i] = field_8_parsed_expr[i].copy();
}
rec.field_8_parsed_expr = ptgs;
rec.specialCachedValue = specialCachedValue;
return rec;
}
} }

View File

@ -209,30 +209,18 @@ public class RecordInputStream extends InputStream {
return result; return result;
} }
byte[] NAN_data = null;
public double readDouble() { public double readDouble() {
checkRecordPosition(); checkRecordPosition();
//Reset NAN data long valueLongBits = LittleEndian.getLong(data, recordOffset);
NAN_data = null; double result = Double.longBitsToDouble(valueLongBits);
double result = LittleEndian.getDouble(data, recordOffset);
//Excel represents NAN in several ways, at this point in time we do not often
//know the sequence of bytes, so as a hack we store the NAN byte sequence
//so that it is not corrupted.
if (Double.isNaN(result)) { if (Double.isNaN(result)) {
NAN_data = new byte[8]; throw new RuntimeException("Did not expect to read NaN");
System.arraycopy(data, recordOffset, NAN_data, 0, 8);
} }
recordOffset += LittleEndian.DOUBLE_SIZE; recordOffset += LittleEndian.DOUBLE_SIZE;
pos += LittleEndian.DOUBLE_SIZE; pos += LittleEndian.DOUBLE_SIZE;
return result; return result;
} }
public byte[] getNANData() {
if (NAN_data == null)
throw new RecordFormatException("Do NOT call getNANData without calling readDouble that returns NaN");
return NAN_data;
}
public short[] readShortArray() { public short[] readShortArray() {
checkRecordPosition(); checkRecordPosition();
@ -276,9 +264,6 @@ public class RecordInputStream extends InputStream {
} }
public String readCompressedUnicode(int length) { public String readCompressedUnicode(int length) {
if(length == 0) {
return "";
}
if ((length < 0) || ((remaining() < length) && !isContinueNext())) { if ((length < 0) || ((remaining() < length) && !isContinueNext())) {
throw new IllegalArgumentException("Illegal length " + length); throw new IllegalArgumentException("Illegal length " + length);
} }
@ -291,9 +276,7 @@ public class RecordInputStream extends InputStream {
if(compressByte != 0) throw new IllegalArgumentException("compressByte in continue records must be 0 while reading compressed unicode"); if(compressByte != 0) throw new IllegalArgumentException("compressByte in continue records must be 0 while reading compressed unicode");
} }
byte b = readByte(); byte b = readByte();
//Typecast direct to char from byte with high bit set causes all ones char ch = (char)(0x00FF & b); // avoid sex
//in the high byte of the char (which is of course incorrect)
char ch = (char)( (short)0xff & (short)b );
buf.append(ch); buf.append(ch);
} }
return buf.toString(); return buf.toString();

View File

@ -20,6 +20,7 @@ package org.apache.poi.hssf.record.aggregates;
import org.apache.poi.hssf.record.CellValueRecordInterface; import org.apache.poi.hssf.record.CellValueRecordInterface;
import org.apache.poi.hssf.record.FormulaRecord; import org.apache.poi.hssf.record.FormulaRecord;
import org.apache.poi.hssf.record.Record; import org.apache.poi.hssf.record.Record;
import org.apache.poi.hssf.record.RecordFormatException;
import org.apache.poi.hssf.record.StringRecord; import org.apache.poi.hssf.record.StringRecord;
/** /**
@ -34,9 +35,9 @@ public final class FormulaRecordAggregate extends RecordAggregate implements Cel
private SharedValueManager _sharedValueManager; private SharedValueManager _sharedValueManager;
/** caches the calculated result of the formula */ /** caches the calculated result of the formula */
private StringRecord _stringRecord; private StringRecord _stringRecord;
/** /**
* @param stringRec may be <code>null</code> if this formula does not have a cached text * @param stringRec may be <code>null</code> if this formula does not have a cached text
* value. * value.
* @param svm the {@link SharedValueManager} for the current sheet * @param svm the {@link SharedValueManager} for the current sheet
*/ */
@ -44,6 +45,14 @@ public final class FormulaRecordAggregate extends RecordAggregate implements Cel
if (svm == null) { if (svm == null) {
throw new IllegalArgumentException("sfm must not be null"); throw new IllegalArgumentException("sfm must not be null");
} }
boolean hasStringRec = stringRec != null;
boolean hasCachedStringFlag = formulaRec.hasCachedResultString();
if (hasStringRec != hasCachedStringFlag) {
throw new RecordFormatException("String record was "
+ (hasStringRec ? "": "not ") + " supplied but formula record flag is "
+ (hasCachedStringFlag ? "" : "not ") + " set");
}
if (formulaRec.isSharedFormula()) { if (formulaRec.isSharedFormula()) {
svm.convertSharedFormulaRecord(formulaRec); svm.convertSharedFormulaRecord(formulaRec);
} }
@ -52,18 +61,18 @@ public final class FormulaRecordAggregate extends RecordAggregate implements Cel
_stringRecord = stringRec; _stringRecord = stringRec;
} }
public void setStringRecord(StringRecord stringRecord) {
_stringRecord = stringRecord;
}
public FormulaRecord getFormulaRecord() { public FormulaRecord getFormulaRecord() {
return _formulaRecord; return _formulaRecord;
} }
/**
* debug only
* TODO - encapsulate
*/
public StringRecord getStringRecord() { public StringRecord getStringRecord() {
return _stringRecord; return _stringRecord;
} }
public short getXFIndex() { public short getXFIndex() {
return _formulaRecord.getXFIndex(); return _formulaRecord.getXFIndex();
} }
@ -91,7 +100,7 @@ public final class FormulaRecordAggregate extends RecordAggregate implements Cel
public String toString() { public String toString() {
return _formulaRecord.toString(); return _formulaRecord.toString();
} }
public void visitContainedRecords(RecordVisitor rv) { public void visitContainedRecords(RecordVisitor rv) {
rv.visitRecord(_formulaRecord); rv.visitRecord(_formulaRecord);
Record sharedFormulaRecord = _sharedValueManager.getRecordForFirstCell(_formulaRecord); Record sharedFormulaRecord = _sharedValueManager.getRecordForFirstCell(_formulaRecord);
@ -102,11 +111,33 @@ public final class FormulaRecordAggregate extends RecordAggregate implements Cel
rv.visitRecord(_stringRecord); rv.visitRecord(_stringRecord);
} }
} }
public String getStringValue() { public String getStringValue() {
if(_stringRecord==null) { if(_stringRecord==null) {
return null; return null;
} }
return _stringRecord.getString(); return _stringRecord.getString();
} }
public void setCachedStringResult(String value) {
// Save the string into a String Record, creating one if required
if(_stringRecord == null) {
_stringRecord = new StringRecord();
}
_stringRecord.setString(value);
if (value.length() < 1) {
_formulaRecord.setCachedResultTypeEmptyString();
} else {
_formulaRecord.setCachedResultTypeString();
}
}
public void setCachedBooleanResult(boolean value) {
_stringRecord = null;
_formulaRecord.setCachedResultBoolean(value);
}
public void setCachedErrorResult(int errorCode) {
_stringRecord = null;
_formulaRecord.setCachedResultErrorCode(errorCode);
}
} }

View File

@ -23,6 +23,7 @@ import java.util.Calendar;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List;
import org.apache.poi.hssf.model.FormulaParser; import org.apache.poi.hssf.model.FormulaParser;
import org.apache.poi.hssf.model.Sheet; import org.apache.poi.hssf.model.Sheet;
@ -55,8 +56,8 @@ import org.apache.poi.hssf.record.formula.eval.ErrorEval;
* Cells can be numeric, formula-based or string-based (text). The cell type * Cells can be numeric, formula-based or string-based (text). The cell type
* specifies this. String cells cannot conatin numbers and numeric cells cannot * specifies this. String cells cannot conatin numbers and numeric cells cannot
* contain strings (at least according to our model). Client apps should do the * contain strings (at least according to our model). Client apps should do the
* conversions themselves. Formula cells have the formula string, as well as * conversions themselves. Formula cells have the formula string, as well as
* the formula result, which can be numeric or string. * the formula result, which can be numeric or string.
* <p> * <p>
* Cells should have their number (0 based) before being added to a row. Only * Cells should have their number (0 based) before being added to a row. Only
* cells that have values should be added. * cells that have values should be added.
@ -82,14 +83,15 @@ public final class HSSFCell {
public final static int CELL_TYPE_BOOLEAN = 4; public final static int CELL_TYPE_BOOLEAN = 4;
/** Error Cell type (5) @see #setCellType(int) @see #getCellType() */ /** Error Cell type (5) @see #setCellType(int) @see #getCellType() */
public final static int CELL_TYPE_ERROR = 5; public final static int CELL_TYPE_ERROR = 5;
public final static short ENCODING_UNCHANGED = -1; public final static short ENCODING_UNCHANGED = -1;
public final static short ENCODING_COMPRESSED_UNICODE = 0; public final static short ENCODING_COMPRESSED_UNICODE = 0;
public final static short ENCODING_UTF_16 = 1; public final static short ENCODING_UTF_16 = 1;
private final HSSFWorkbook book;
private final Sheet sheet;
private int cellType; private int cellType;
private HSSFRichTextString stringValue; private HSSFRichTextString stringValue;
private HSSFWorkbook book;
private Sheet sheet;
private CellValueRecordInterface record; private CellValueRecordInterface record;
private HSSFComment comment; private HSSFComment comment;
@ -122,6 +124,9 @@ public final class HSSFCell {
short xfindex = sheet.getXFIndexForColAt(col); short xfindex = sheet.getXFIndexForColAt(col);
setCellType(CELL_TYPE_BLANK, false, row, col,xfindex); setCellType(CELL_TYPE_BLANK, false, row, col,xfindex);
} }
/* package */ Sheet getSheet() {
return sheet;
}
/** /**
* Creates new Cell - Should only be called by HSSFRow. This creates a cell * Creates new Cell - Should only be called by HSSFRow. This creates a cell
@ -144,7 +149,7 @@ public final class HSSFCell {
stringValue = null; stringValue = null;
this.book = book; this.book = book;
this.sheet = sheet; this.sheet = sheet;
short xfindex = sheet.getXFIndexForColAt(col); short xfindex = sheet.getXFIndexForColAt(col);
setCellType(type,false,row,col,xfindex); setCellType(type,false,row,col,xfindex);
} }
@ -186,10 +191,10 @@ public final class HSSFCell {
* used internally -- given a cell value record, figure out its type * used internally -- given a cell value record, figure out its type
*/ */
private static int determineType(CellValueRecordInterface cval) { private static int determineType(CellValueRecordInterface cval) {
if (cval instanceof FormulaRecordAggregate) { if (cval instanceof FormulaRecordAggregate) {
return HSSFCell.CELL_TYPE_FORMULA; return HSSFCell.CELL_TYPE_FORMULA;
} }
// all others are plain BIFF records // all others are plain BIFF records
Record record = ( Record ) cval; Record record = ( Record ) cval;
switch (record.getSid()) { switch (record.getSid()) {
@ -205,13 +210,13 @@ public final class HSSFCell {
} }
throw new RuntimeException("Bad cell value rec (" + cval.getClass().getName() + ")"); throw new RuntimeException("Bad cell value rec (" + cval.getClass().getName() + ")");
} }
/** /**
* Returns the Workbook that this Cell is bound to * Returns the Workbook that this Cell is bound to
* @return * @return
*/ */
protected Workbook getBoundWorkbook() { protected Workbook getBoundWorkbook() {
return book.getWorkbook(); return book.getWorkbook();
} }
/** /**
@ -229,7 +234,7 @@ public final class HSSFCell {
{ {
record.setColumn(num); record.setColumn(num);
} }
/** /**
* Updates the cell record's idea of what * Updates the cell record's idea of what
* column it belongs in (0 based) * column it belongs in (0 based)
@ -237,7 +242,7 @@ public final class HSSFCell {
*/ */
protected void updateCellNum(short num) protected void updateCellNum(short num)
{ {
record.setColumn(num); record.setColumn(num);
} }
/** /**
@ -417,14 +422,14 @@ public final class HSSFCell {
errRec.setColumn(col); errRec.setColumn(col);
if (setValue) if (setValue)
{ {
errRec.setValue(getErrorCellValue()); errRec.setValue((byte)HSSFErrorConstants.ERROR_VALUE);
} }
errRec.setXFIndex(styleIndex); errRec.setXFIndex(styleIndex);
errRec.setRow(row); errRec.setRow(row);
record = errRec; record = errRec;
break; break;
} }
if (cellType != this.cellType && if (cellType != this.cellType &&
this.cellType!=-1 ) // Special Value to indicate an uninitialized Cell this.cellType!=-1 ) // Special Value to indicate an uninitialized Cell
{ {
sheet.replaceValueRecord(record); sheet.replaceValueRecord(record);
@ -453,21 +458,20 @@ public final class HSSFCell {
* precalculated value, for numerics we'll set its value. For other types we * precalculated value, for numerics we'll set its value. For other types we
* will change the cell to a numeric cell and set its value. * will change the cell to a numeric cell and set its value.
*/ */
public void setCellValue(double value) public void setCellValue(double value) {
{
int row=record.getRow(); int row=record.getRow();
short col=record.getColumn(); short col=record.getColumn();
short styleIndex=record.getXFIndex(); short styleIndex=record.getXFIndex();
if ((cellType != CELL_TYPE_NUMERIC) && (cellType != CELL_TYPE_FORMULA))
{ switch (cellType) {
setCellType(CELL_TYPE_NUMERIC, false, row, col, styleIndex); default:
} setCellType(CELL_TYPE_NUMERIC, false, row, col, styleIndex);
case CELL_TYPE_ERROR:
// Save into the appropriate record (( NumberRecord ) record).setValue(value);
if(record instanceof FormulaRecordAggregate) { break;
(( FormulaRecordAggregate ) record).getFormulaRecord().setValue(value); case CELL_TYPE_FORMULA:
} else { ((FormulaRecordAggregate)record).getFormulaRecord().setValue(value);
(( NumberRecord ) record).setValue(value); break;
} }
} }
@ -487,7 +491,7 @@ public final class HSSFCell {
/** /**
* set a date value for the cell. Excel treats dates as numeric so you will need to format the cell as * set a date value for the cell. Excel treats dates as numeric so you will need to format the cell as
* a date. * a date.
* *
* This will set the cell value based on the Calendar's timezone. As Excel * This will set the cell value based on the Calendar's timezone. As Excel
* does not support timezones this means that both 20:00+03:00 and * does not support timezones this means that both 20:00+03:00 and
* 20:00-03:00 will be reported as the same value (20:00) even that there * 20:00-03:00 will be reported as the same value (20:00) even that there
@ -539,31 +543,20 @@ public final class HSSFCell {
return; return;
} }
if (cellType == CELL_TYPE_FORMULA) { if (cellType == CELL_TYPE_FORMULA) {
// Set the 'pre-evaluated result' for the formula // Set the 'pre-evaluated result' for the formula
// note - formulas do not preserve text formatting. // note - formulas do not preserve text formatting.
FormulaRecordAggregate fr = (FormulaRecordAggregate) record; FormulaRecordAggregate fr = (FormulaRecordAggregate) record;
fr.setCachedStringResult(value.getString());
// Save the string into a String Record, creating
// one if required
StringRecord sr = fr.getStringRecord();
if(sr == null) {
// Wasn't a string before, need a new one
sr = new StringRecord();
fr.setStringRecord(sr);
}
// Save, loosing the formatting
sr.setString(value.getString());
// Update our local cache to the un-formatted version // Update our local cache to the un-formatted version
stringValue = new HSSFRichTextString(sr.getString()); stringValue = new HSSFRichTextString(value.getString());
// All done // All done
return; return;
} }
// If we get here, we're not dealing with a formula, // If we get here, we're not dealing with a formula,
// so handle things as a normal rich text cell // so handle things as a normal rich text cell
if (cellType != CELL_TYPE_STRING) { if (cellType != CELL_TYPE_STRING) {
setCellType(CELL_TYPE_STRING, false, row, col, styleIndex); setCellType(CELL_TYPE_STRING, false, row, col, styleIndex);
} }
@ -591,95 +584,95 @@ public final class HSSFCell {
FormulaRecord frec = rec.getFormulaRecord(); FormulaRecord frec = rec.getFormulaRecord();
frec.setOptions((short) 2); frec.setOptions((short) 2);
frec.setValue(0); frec.setValue(0);
//only set to default if there is no extended format index already set //only set to default if there is no extended format index already set
if (rec.getXFIndex() == (short)0) { if (rec.getXFIndex() == (short)0) {
rec.setXFIndex((short) 0x0f); rec.setXFIndex((short) 0x0f);
} }
Ptg[] ptgs = FormulaParser.parse(formula, book); Ptg[] ptgs = FormulaParser.parse(formula, book);
frec.setParsedExpression(ptgs); frec.setParsedExpression(ptgs);
} }
/* package */ void setFormulaOnly(Ptg[] ptgs) {
if (ptgs == null) {
throw new IllegalArgumentException("ptgs must not be null");
}
((FormulaRecordAggregate)record).getFormulaRecord().setParsedExpression(ptgs);
}
public String getCellFormula() { public String getCellFormula() {
return FormulaParser.toFormulaString(book, ((FormulaRecordAggregate)record).getFormulaRecord().getParsedExpression()); return FormulaParser.toFormulaString(book, ((FormulaRecordAggregate)record).getFormulaRecord().getParsedExpression());
} }
/**
* Used to help format error messages
*/
private static String getCellTypeName(int cellTypeCode) {
switch (cellTypeCode) {
case CELL_TYPE_BLANK: return "blank";
case CELL_TYPE_STRING: return "text";
case CELL_TYPE_BOOLEAN: return "boolean";
case CELL_TYPE_ERROR: return "error";
case CELL_TYPE_NUMERIC: return "numeric";
case CELL_TYPE_FORMULA: return "formula";
}
return "#unknown cell type (" + cellTypeCode + ")#";
}
private static RuntimeException typeMismatch(int expectedTypeCode, int actualTypeCode, boolean isFormulaCell) {
String msg = "Cannot get a "
+ getCellTypeName(expectedTypeCode) + " value from a "
+ getCellTypeName(actualTypeCode) + " " + (isFormulaCell ? "formula " : "") + "cell";
return new IllegalStateException(msg);
}
private static void checkFormulaCachedValueType(int expectedTypeCode, FormulaRecord fr) {
int cachedValueType = fr.getCachedResultType();
if (cachedValueType != expectedTypeCode) {
throw typeMismatch(expectedTypeCode, cachedValueType, true);
}
}
/** /**
* Get the value of the cell as a number. * Get the value of the cell as a number.
* For strings we throw an exception. * For strings we throw an exception.
* For blank cells we return a 0. * For blank cells we return a 0.
* See {@link HSSFDataFormatter} for turning this * See {@link HSSFDataFormatter} for turning this
* number into a string similar to that which * number into a string similar to that which
* Excel would render this number as. * Excel would render this number as.
*/ */
public double getNumericCellValue() public double getNumericCellValue() {
{
if (cellType == CELL_TYPE_BLANK) switch(cellType) {
{ case CELL_TYPE_BLANK:
return 0; return 0.0;
case CELL_TYPE_NUMERIC:
return ((NumberRecord)record).getValue();
default:
throw typeMismatch(CELL_TYPE_NUMERIC, cellType, false);
case CELL_TYPE_FORMULA:
break;
} }
if (cellType == CELL_TYPE_STRING) FormulaRecord fr = ((FormulaRecordAggregate)record).getFormulaRecord();
{ checkFormulaCachedValueType(CELL_TYPE_NUMERIC, fr);
throw new NumberFormatException( return fr.getValue();
"You cannot get a numeric value from a String based cell");
}
if (cellType == CELL_TYPE_BOOLEAN)
{
throw new NumberFormatException(
"You cannot get a numeric value from a boolean cell");
}
if (cellType == CELL_TYPE_ERROR)
{
throw new NumberFormatException(
"You cannot get a numeric value from an error cell");
}
if(cellType == CELL_TYPE_NUMERIC)
{
return ((NumberRecord)record).getValue();
}
if(cellType == CELL_TYPE_FORMULA)
{
return ((FormulaRecordAggregate)record).getFormulaRecord().getValue();
}
throw new NumberFormatException("Unknown Record Type in Cell:"+cellType);
} }
/** /**
* Get the value of the cell as a date. * Get the value of the cell as a date.
* For strings we throw an exception. * For strings we throw an exception.
* For blank cells we return a null. * For blank cells we return a null.
* See {@link HSSFDataFormatter} for formatting * See {@link HSSFDataFormatter} for formatting
* this date into a string similar to how excel does. * this date into a string similar to how excel does.
*/ */
public Date getDateCellValue() public Date getDateCellValue() {
{
if (cellType == CELL_TYPE_BLANK) if (cellType == CELL_TYPE_BLANK) {
{
return null; return null;
} }
if (cellType == CELL_TYPE_STRING) double value = getNumericCellValue();
{
throw new NumberFormatException(
"You cannot get a date value from a String based cell");
}
if (cellType == CELL_TYPE_BOOLEAN)
{
throw new NumberFormatException(
"You cannot get a date value from a boolean cell");
}
if (cellType == CELL_TYPE_ERROR)
{
throw new NumberFormatException(
"You cannot get a date value from an error cell");
}
double value=this.getNumericCellValue();
if (book.getWorkbook().isUsing1904DateWindowing()) { if (book.getWorkbook().isUsing1904DateWindowing()) {
return HSSFDateUtil.getJavaDate(value,true); return HSSFDateUtil.getJavaDate(value, true);
}
else {
return HSSFDateUtil.getJavaDate(value,false);
} }
return HSSFDateUtil.getJavaDate(value, false);
} }
/** /**
@ -700,33 +693,22 @@ public final class HSSFCell {
* For blank cells we return an empty string. * For blank cells we return an empty string.
* For formulaCells that are not string Formulas, we return empty String * For formulaCells that are not string Formulas, we return empty String
*/ */
public HSSFRichTextString getRichStringCellValue() {
public HSSFRichTextString getRichStringCellValue() switch(cellType) {
{ case CELL_TYPE_BLANK:
if (cellType == CELL_TYPE_BLANK) return new HSSFRichTextString("");
{ case CELL_TYPE_STRING:
return new HSSFRichTextString(""); return stringValue;
default:
throw typeMismatch(CELL_TYPE_STRING, cellType, false);
case CELL_TYPE_FORMULA:
break;
} }
if (cellType == CELL_TYPE_NUMERIC) FormulaRecordAggregate fra = ((FormulaRecordAggregate)record);
{ checkFormulaCachedValueType(CELL_TYPE_STRING, fra.getFormulaRecord());
throw new NumberFormatException( String strVal = fra.getStringValue();
"You cannot get a string value from a numeric cell"); return new HSSFRichTextString(strVal == null ? "" : strVal);
}
if (cellType == CELL_TYPE_BOOLEAN)
{
throw new NumberFormatException(
"You cannot get a string value from a boolean cell");
}
if (cellType == CELL_TYPE_ERROR)
{
throw new NumberFormatException(
"You cannot get a string value from an error cell");
}
if (cellType == CELL_TYPE_FORMULA)
{
if (stringValue==null) return new HSSFRichTextString("");
}
return stringValue;
} }
/** /**
@ -737,47 +719,56 @@ public final class HSSFCell {
* will change the cell to a boolean cell and set its value. * will change the cell to a boolean cell and set its value.
*/ */
public void setCellValue(boolean value) public void setCellValue(boolean value) {
{
int row=record.getRow(); int row=record.getRow();
short col=record.getColumn(); short col=record.getColumn();
short styleIndex=record.getXFIndex(); short styleIndex=record.getXFIndex();
if ((cellType != CELL_TYPE_BOOLEAN ) && ( cellType != CELL_TYPE_FORMULA))
{ switch (cellType) {
setCellType(CELL_TYPE_BOOLEAN, false, row, col, styleIndex); default:
setCellType(CELL_TYPE_BOOLEAN, false, row, col, styleIndex);
case CELL_TYPE_ERROR:
(( BoolErrRecord ) record).setValue(value);
break;
case CELL_TYPE_FORMULA:
((FormulaRecordAggregate)record).getFormulaRecord().setCachedResultBoolean(value);
break;
} }
(( BoolErrRecord ) record).setValue(value);
} }
/** /**
* set a error value for the cell * set a error value for the cell
* *
* @param value the error value to set this cell to. For formulas we'll set the * @param errorCode the error value to set this cell to. For formulas we'll set the
* precalculated value ??? IS THIS RIGHT??? , for errors we'll set * precalculated value , for errors we'll set
* its value. For other types we will change the cell to an error * its value. For other types we will change the cell to an error
* cell and set its value. * cell and set its value.
*/ */
public void setCellErrorValue(byte errorCode) {
public void setCellErrorValue(byte value)
{
int row=record.getRow(); int row=record.getRow();
short col=record.getColumn(); short col=record.getColumn();
short styleIndex=record.getXFIndex(); short styleIndex=record.getXFIndex();
if (cellType != CELL_TYPE_ERROR) { switch (cellType) {
setCellType(CELL_TYPE_ERROR, false, row, col, styleIndex); default:
setCellType(CELL_TYPE_ERROR, false, row, col, styleIndex);
case CELL_TYPE_ERROR:
(( BoolErrRecord ) record).setValue(errorCode);
break;
case CELL_TYPE_FORMULA:
((FormulaRecordAggregate)record).getFormulaRecord().setCachedResultErrorCode(errorCode);
break;
} }
(( BoolErrRecord ) record).setValue(value);
} }
/** /**
* Chooses a new boolean value for the cell when its type is changing.<p/> * Chooses a new boolean value for the cell when its type is changing.<p/>
* *
* Usually the caller is calling setCellType() with the intention of calling * Usually the caller is calling setCellType() with the intention of calling
* setCellValue(boolean) straight afterwards. This method only exists to give * setCellValue(boolean) straight afterwards. This method only exists to give
* the cell a somewhat reasonable value until the setCellValue() call (if at all). * the cell a somewhat reasonable value until the setCellValue() call (if at all).
* TODO - perhaps a method like setCellTypeAndValue(int, Object) should be introduced to avoid this * TODO - perhaps a method like setCellTypeAndValue(int, Object) should be introduced to avoid this
*/ */
private boolean convertCellValueToBoolean() { private boolean convertCellValueToBoolean() {
switch (cellType) { switch (cellType) {
case CELL_TYPE_BOOLEAN: case CELL_TYPE_BOOLEAN:
return (( BoolErrRecord ) record).getBooleanValue(); return (( BoolErrRecord ) record).getBooleanValue();
@ -788,11 +779,11 @@ public final class HSSFCell {
// All other cases convert to false // All other cases convert to false
// These choices are not well justified. // These choices are not well justified.
case CELL_TYPE_FORMULA: case CELL_TYPE_FORMULA:
// should really evaluate, but HSSFCell can't call HSSFFormulaEvaluator // should really evaluate, but HSSFCell can't call HSSFFormulaEvaluator
case CELL_TYPE_ERROR: case CELL_TYPE_ERROR:
case CELL_TYPE_BLANK: case CELL_TYPE_BLANK:
return false; return false;
} }
throw new RuntimeException("Unexpected cell type (" + cellType + ")"); throw new RuntimeException("Unexpected cell type (" + cellType + ")");
} }
@ -801,38 +792,39 @@ public final class HSSFCell {
* get the value of the cell as a boolean. For strings, numbers, and errors, we throw an exception. * get the value of the cell as a boolean. For strings, numbers, and errors, we throw an exception.
* For blank cells we return a false. * For blank cells we return a false.
*/ */
public boolean getBooleanCellValue() {
public boolean getBooleanCellValue() switch(cellType) {
{ case CELL_TYPE_BLANK:
if (cellType == CELL_TYPE_BOOLEAN) return false;
{ case CELL_TYPE_BOOLEAN:
return (( BoolErrRecord ) record).getBooleanValue(); return (( BoolErrRecord ) record).getBooleanValue();
default:
throw typeMismatch(CELL_TYPE_BOOLEAN, cellType, false);
case CELL_TYPE_FORMULA:
break;
} }
if (cellType == CELL_TYPE_BLANK) FormulaRecord fr = ((FormulaRecordAggregate)record).getFormulaRecord();
{ checkFormulaCachedValueType(CELL_TYPE_BOOLEAN, fr);
return false; return fr.getCachedBooleanValue();
}
throw new NumberFormatException(
"You cannot get a boolean value from a non-boolean cell");
} }
/** /**
* get the value of the cell as an error code. For strings, numbers, and booleans, we throw an exception. * get the value of the cell as an error code. For strings, numbers, and booleans, we throw an exception.
* For blank cells we return a 0. * For blank cells we return a 0.
*/ */
public byte getErrorCellValue() {
public byte getErrorCellValue() switch(cellType) {
{ case CELL_TYPE_ERROR:
if (cellType == CELL_TYPE_ERROR) return (( BoolErrRecord ) record).getErrorValue();
{ default:
return (( BoolErrRecord ) record).getErrorValue(); throw typeMismatch(CELL_TYPE_ERROR, cellType, false);
case CELL_TYPE_FORMULA:
break;
} }
if (cellType == CELL_TYPE_BLANK) FormulaRecord fr = ((FormulaRecordAggregate)record).getFormulaRecord();
{ checkFormulaCachedValueType(CELL_TYPE_ERROR, fr);
return ( byte ) 0; return (byte) fr.getCachedErrorValue();
}
throw new NumberFormatException(
"You cannot get an error value from a non-error cell");
} }
/** /**
@ -888,7 +880,7 @@ public final class HSSFCell {
throw new RuntimeException("You cannot reference columns with an index of less then 0."); throw new RuntimeException("You cannot reference columns with an index of less then 0.");
} }
} }
/** /**
* Sets this cell as the active cell for the worksheet * Sets this cell as the active cell for the worksheet
*/ */
@ -899,42 +891,42 @@ public final class HSSFCell {
this.sheet.setActiveCellRow(row); this.sheet.setActiveCellRow(row);
this.sheet.setActiveCellCol(col); this.sheet.setActiveCellCol(col);
} }
/** /**
* Returns a string representation of the cell * Returns a string representation of the cell
* *
* This method returns a simple representation, * This method returns a simple representation,
* anthing more complex should be in user code, with * anthing more complex should be in user code, with
* knowledge of the semantics of the sheet being processed. * knowledge of the semantics of the sheet being processed.
* *
* Formula cells return the formula string, * Formula cells return the formula string,
* rather than the formula result. * rather than the formula result.
* Dates are displayed in dd-MMM-yyyy format * Dates are displayed in dd-MMM-yyyy format
* Errors are displayed as #ERR&lt;errIdx&gt; * Errors are displayed as #ERR&lt;errIdx&gt;
*/ */
public String toString() { public String toString() {
switch (getCellType()) { switch (getCellType()) {
case CELL_TYPE_BLANK: case CELL_TYPE_BLANK:
return ""; return "";
case CELL_TYPE_BOOLEAN: case CELL_TYPE_BOOLEAN:
return getBooleanCellValue()?"TRUE":"FALSE"; return getBooleanCellValue()?"TRUE":"FALSE";
case CELL_TYPE_ERROR: case CELL_TYPE_ERROR:
return ErrorEval.getText((( BoolErrRecord ) record).getErrorValue()); return ErrorEval.getText((( BoolErrRecord ) record).getErrorValue());
case CELL_TYPE_FORMULA: case CELL_TYPE_FORMULA:
return getCellFormula(); return getCellFormula();
case CELL_TYPE_NUMERIC: case CELL_TYPE_NUMERIC:
//TODO apply the dataformat for this cell //TODO apply the dataformat for this cell
if (HSSFDateUtil.isCellDateFormatted(this)) { if (HSSFDateUtil.isCellDateFormatted(this)) {
DateFormat sdf = new SimpleDateFormat("dd-MMM-yyyy"); DateFormat sdf = new SimpleDateFormat("dd-MMM-yyyy");
return sdf.format(getDateCellValue()); return sdf.format(getDateCellValue());
} else { } else {
return getNumericCellValue() + ""; return getNumericCellValue() + "";
} }
case CELL_TYPE_STRING: case CELL_TYPE_STRING:
return getStringCellValue(); return getStringCellValue();
default: default:
return "Unknown Cell Type: " + getCellType(); return "Unknown Cell Type: " + getCellType();
} }
} }
/** /**
@ -945,11 +937,11 @@ public final class HSSFCell {
* @param comment comment associated with this cell * @param comment comment associated with this cell
*/ */
public void setCellComment(HSSFComment comment){ public void setCellComment(HSSFComment comment){
if(comment == null) { if(comment == null) {
removeCellComment(); removeCellComment();
return; return;
} }
comment.setRow((short)record.getRow()); comment.setRow((short)record.getRow());
comment.setColumn(record.getColumn()); comment.setColumn(record.getColumn());
this.comment = comment; this.comment = comment;
@ -966,7 +958,7 @@ public final class HSSFCell {
} }
return comment; return comment;
} }
/** /**
* Removes the comment for this cell, if * Removes the comment for this cell, if
* there is one. * there is one.
@ -974,40 +966,41 @@ public final class HSSFCell {
* all comments after performing this action! * all comments after performing this action!
*/ */
public void removeCellComment() { public void removeCellComment() {
HSSFComment comment = findCellComment(sheet, record.getRow(), record.getColumn()); HSSFComment comment = findCellComment(sheet, record.getRow(), record.getColumn());
this.comment = null; this.comment = null;
if(comment == null) { if(comment == null) {
// Nothing to do // Nothing to do
return; return;
} }
// Zap the underlying NoteRecord // Zap the underlying NoteRecord
sheet.getRecords().remove(comment.getNoteRecord()); List sheetRecords = sheet.getRecords();
sheetRecords.remove(comment.getNoteRecord());
// If we have a TextObjectRecord, is should
// be proceeed by: // If we have a TextObjectRecord, is should
// MSODRAWING with container // be proceeed by:
// OBJ // MSODRAWING with container
// MSODRAWING with EscherTextboxRecord // OBJ
if(comment.getTextObjectRecord() != null) { // MSODRAWING with EscherTextboxRecord
TextObjectRecord txo = comment.getTextObjectRecord(); if(comment.getTextObjectRecord() != null) {
int txoAt = sheet.getRecords().indexOf(txo); TextObjectRecord txo = comment.getTextObjectRecord();
int txoAt = sheetRecords.indexOf(txo);
if(sheet.getRecords().get(txoAt-3) instanceof DrawingRecord &&
sheet.getRecords().get(txoAt-2) instanceof ObjRecord && if(sheetRecords.get(txoAt-3) instanceof DrawingRecord &&
sheet.getRecords().get(txoAt-1) instanceof DrawingRecord) { sheetRecords.get(txoAt-2) instanceof ObjRecord &&
// Zap these, in reverse order sheetRecords.get(txoAt-1) instanceof DrawingRecord) {
sheet.getRecords().remove(txoAt-1); // Zap these, in reverse order
sheet.getRecords().remove(txoAt-2); sheetRecords.remove(txoAt-1);
sheet.getRecords().remove(txoAt-3); sheetRecords.remove(txoAt-2);
} else { sheetRecords.remove(txoAt-3);
throw new IllegalStateException("Found the wrong records before the TextObjectRecord, can't remove comment"); } else {
} throw new IllegalStateException("Found the wrong records before the TextObjectRecord, can't remove comment");
}
// Now remove the text record
sheet.getRecords().remove(txo); // Now remove the text record
} sheetRecords.remove(txo);
}
} }
/** /**
@ -1100,4 +1093,16 @@ public final class HSSFCell {
int eofLoc = sheet.findFirstRecordLocBySid( EOFRecord.sid ); int eofLoc = sheet.findFirstRecordLocBySid( EOFRecord.sid );
sheet.getRecords().add( eofLoc, link.record ); sheet.getRecords().add( eofLoc, link.record );
} }
/**
* Only valid for formula cells
* @return one of ({@link #CELL_TYPE_NUMERIC}, {@link #CELL_TYPE_STRING},
* {@link #CELL_TYPE_BOOLEAN}, {@link #CELL_TYPE_ERROR}) depending
* on the cached value of the formula
*/
public int getCachedFormulaResultType() {
if (this.cellType != CELL_TYPE_FORMULA) {
throw new IllegalStateException("Only formula cells have cached results");
}
return ((FormulaRecordAggregate)record).getFormulaRecord().getCachedResultType();
}
} }

View File

@ -1295,9 +1295,7 @@ public final class HSSFSheet {
// If any references were changed, then // If any references were changed, then
// re-create the formula string // re-create the formula string
if(changed) { if(changed) {
c.setCellFormula( c.setFormulaOnly(ptgs);
FormulaParser.toFormulaString(workbook, ptgs)
);
} }
} }
} }

View File

@ -660,6 +660,16 @@ public class HSSFWorkbook extends POIDocument
return -1; return -1;
} }
/* package */ int findSheetIndex(Sheet sheet) {
for(int i=0; i<_sheets.size(); i++) {
HSSFSheet hSheet = (HSSFSheet) _sheets.get(i);
if(hSheet.getSheet() == sheet) {
return i;
}
}
throw new IllegalArgumentException("Specified sheet not found in this workbook");
}
/** /**
* Returns the external sheet index of the sheet * Returns the external sheet index of the sheet
* with the given internal index, creating one * with the given internal index, creating one

View File

@ -214,7 +214,9 @@ public final class TestSheet extends TestCase {
records.add(new DimensionsRecord()); records.add(new DimensionsRecord());
records.add(new RowRecord(0)); records.add(new RowRecord(0));
records.add(new RowRecord(1)); records.add(new RowRecord(1));
records.add(new FormulaRecord()); FormulaRecord formulaRecord = new FormulaRecord();
formulaRecord.setCachedResultTypeString();
records.add(formulaRecord);
records.add(new StringRecord()); records.add(new StringRecord());
records.add(new RowRecord(2)); records.add(new RowRecord(2));
records.add(createWindow2Record()); records.add(createWindow2Record());

View File

@ -26,12 +26,14 @@ import org.apache.poi.hssf.record.formula.FuncVarPtg;
import org.apache.poi.hssf.record.formula.IntPtg; import org.apache.poi.hssf.record.formula.IntPtg;
import org.apache.poi.hssf.record.formula.Ptg; import org.apache.poi.hssf.record.formula.Ptg;
import org.apache.poi.hssf.record.formula.RefPtg; import org.apache.poi.hssf.record.formula.RefPtg;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFErrorConstants;
/** /**
* Tests the serialization and deserialization of the FormulaRecord * Tests the serialization and deserialization of the FormulaRecord
* class works correctly. * class works correctly.
* *
* @author Andrew C. Oliver * @author Andrew C. Oliver
*/ */
public final class TestFormulaRecord extends TestCase { public final class TestFormulaRecord extends TestCase {
@ -40,52 +42,66 @@ public final class TestFormulaRecord extends TestCase {
record.setColumn((short)0); record.setColumn((short)0);
record.setRow(1); record.setRow(1);
record.setXFIndex((short)4); record.setXFIndex((short)4);
assertEquals(record.getColumn(),0); assertEquals(record.getColumn(),0);
assertEquals(record.getRow(), 1); assertEquals(record.getRow(), 1);
assertEquals(record.getXFIndex(),4); assertEquals(record.getXFIndex(),4);
} }
/** /**
* Make sure a NAN value is preserved * Make sure a NAN value is preserved
* This formula record is a representation of =1/0 at row 0, column 0 * This formula record is a representation of =1/0 at row 0, column 0
*/ */
public void testCheckNanPreserve() { public void testCheckNanPreserve() {
byte[] formulaByte = new byte[29]; byte[] formulaByte = {
0, 0, 0, 0,
0x0F, 0x00,
// 8 bytes cached number is a 'special value' in this case
0x02, // special cached value type 'error'
0x00,
HSSFErrorConstants.ERROR_DIV_0,
0x00,
0x00,
0x00,
(byte)0xFF,
(byte)0xFF,
0x00,
0x00,
0x00,
0x00,
(byte)0xE0, //18
(byte)0xFC,
// Ptgs
0x07, 0x00, // encoded length
0x1E, 0x01, 0x00, // IntPtg(1)
0x1E, 0x00, 0x00, // IntPtg(0)
0x06, // DividePtg
};
formulaByte[4] = (byte)0x0F;
formulaByte[6] = (byte)0x02;
formulaByte[8] = (byte)0x07;
formulaByte[12] = (byte)0xFF;
formulaByte[13] = (byte)0xFF;
formulaByte[18] = (byte)0xE0;
formulaByte[19] = (byte)0xFC;
formulaByte[20] = (byte)0x07;
formulaByte[22] = (byte)0x1E;
formulaByte[23] = (byte)0x01;
formulaByte[25] = (byte)0x1E;
formulaByte[28] = (byte)0x06;
FormulaRecord record = new FormulaRecord(new TestcaseRecordInputStream(FormulaRecord.sid, (short)29, formulaByte)); FormulaRecord record = new FormulaRecord(new TestcaseRecordInputStream(FormulaRecord.sid, (short)29, formulaByte));
assertEquals("Row", 0, record.getRow()); assertEquals("Row", 0, record.getRow());
assertEquals("Column", 0, record.getColumn()); assertEquals("Column", 0, record.getColumn());
assertTrue("Value is not NaN", Double.isNaN(record.getValue())); assertEquals(HSSFCell.CELL_TYPE_ERROR, record.getCachedResultType());
byte[] output = record.serialize(); byte[] output = record.serialize();
assertEquals("Output size", 33, output.length); //includes sid+recordlength assertEquals("Output size", 33, output.length); //includes sid+recordlength
for (int i = 5; i < 13;i++) { for (int i = 5; i < 13;i++) {
assertEquals("FormulaByte NaN doesn't match", formulaByte[i], output[i+4]); assertEquals("FormulaByte NaN doesn't match", formulaByte[i], output[i+4]);
} }
} }
/** /**
* Tests to see if the shared formula cells properly reserialize the expPtg * Tests to see if the shared formula cells properly reserialize the expPtg
* *
*/ */
public void testExpFormula() { public void testExpFormula() {
byte[] formulaByte = new byte[27]; byte[] formulaByte = new byte[27];
formulaByte[4] =(byte)0x0F; formulaByte[4] =(byte)0x0F;
formulaByte[14]=(byte)0x08; formulaByte[14]=(byte)0x08;
formulaByte[18]=(byte)0xE0; formulaByte[18]=(byte)0xE0;
@ -99,13 +115,13 @@ public final class TestFormulaRecord extends TestCase {
assertEquals("Output size", 31, output.length); //includes sid+recordlength assertEquals("Output size", 31, output.length); //includes sid+recordlength
assertEquals("Offset 22", 1, output[26]); assertEquals("Offset 22", 1, output[26]);
} }
public void testWithConcat() { public void testWithConcat() {
// =CHOOSE(2,A2,A3,A4) // =CHOOSE(2,A2,A3,A4)
byte[] data = { byte[] data = {
6, 0, 68, 0, 6, 0, 68, 0,
1, 0, 1, 0, 15, 0, 0, 0, 0, 0, 0, 0, 57, 1, 0, 1, 0, 15, 0, 0, 0, 0, 0, 0, 0, 57,
64, 0, 0, 12, 0, 12, -4, 46, 0, 64, 0, 0, 12, 0, 12, -4, 46, 0,
30, 2, 0, // Int - 2 30, 2, 0, // Int - 2
25, 4, 3, 0, // Attr 25, 4, 3, 0, // Attr
8, 0, 17, 0, 26, 0, // jumpTable 8, 0, 17, 0, 26, 0, // jumpTable
@ -115,14 +131,14 @@ public final class TestFormulaRecord extends TestCase {
36, 2, 0, 0, -64, // Ref - A3 36, 2, 0, 0, -64, // Ref - A3
25, 8, 12, 0, // Attr 25, 8, 12, 0, // Attr
36, 3, 0, 0, -64, // Ref - A4 36, 3, 0, 0, -64, // Ref - A4
25, 8, 3, 0, // Attr 25, 8, 3, 0, // Attr
66, 4, 100, 0 // CHOOSE 66, 4, 100, 0 // CHOOSE
}; };
RecordInputStream inp = new RecordInputStream( new ByteArrayInputStream(data)); RecordInputStream inp = new RecordInputStream( new ByteArrayInputStream(data));
inp.nextRecord(); inp.nextRecord();
FormulaRecord fr = new FormulaRecord(inp); FormulaRecord fr = new FormulaRecord(inp);
Ptg[] ptgs = fr.getParsedExpression(); Ptg[] ptgs = fr.getParsedExpression();
assertEquals(9, ptgs.length); assertEquals(9, ptgs.length);
assertEquals(IntPtg.class, ptgs[0].getClass()); assertEquals(IntPtg.class, ptgs[0].getClass());
@ -134,7 +150,7 @@ public final class TestFormulaRecord extends TestCase {
assertEquals(RefPtg.class, ptgs[6].getClass()); assertEquals(RefPtg.class, ptgs[6].getClass());
assertEquals(AttrPtg.class, ptgs[7].getClass()); assertEquals(AttrPtg.class, ptgs[7].getClass());
assertEquals(FuncVarPtg.class, ptgs[8].getClass()); assertEquals(FuncVarPtg.class, ptgs[8].getClass());
FuncVarPtg choose = (FuncVarPtg)ptgs[8]; FuncVarPtg choose = (FuncVarPtg)ptgs[8];
assertEquals("CHOOSE", choose.getName()); assertEquals("CHOOSE", choose.getName());
} }

View File

@ -30,6 +30,7 @@ public final class TestFormulaRecordAggregate extends TestCase {
public void testBasic() throws Exception { public void testBasic() throws Exception {
FormulaRecord f = new FormulaRecord(); FormulaRecord f = new FormulaRecord();
f.setCachedResultTypeString();
StringRecord s = new StringRecord(); StringRecord s = new StringRecord();
s.setString("abc"); s.setString("abc");
FormulaRecordAggregate fagg = new FormulaRecordAggregate(f, s, SharedValueManager.EMPTY); FormulaRecordAggregate fagg = new FormulaRecordAggregate(f, s, SharedValueManager.EMPTY);

View File

@ -961,7 +961,7 @@ public final class TestBugs extends TestCase {
writeOutAndReadBack(wb); writeOutAndReadBack(wb);
assertTrue("no errors writing sample xls", true); assertTrue("no errors writing sample xls", true);
} }
/** /**
* Problems with extracting check boxes from * Problems with extracting check boxes from
* HSSFObjectData * HSSFObjectData
@ -973,35 +973,35 @@ public final class TestBugs extends TestCase {
// Take a look at the embeded objects // Take a look at the embeded objects
List objects = wb.getAllEmbeddedObjects(); List objects = wb.getAllEmbeddedObjects();
assertEquals(1, objects.size()); assertEquals(1, objects.size());
HSSFObjectData obj = (HSSFObjectData)objects.get(0); HSSFObjectData obj = (HSSFObjectData)objects.get(0);
assertNotNull(obj); assertNotNull(obj);
// Peek inside the underlying record // Peek inside the underlying record
EmbeddedObjectRefSubRecord rec = obj.findObjectRecord(); EmbeddedObjectRefSubRecord rec = obj.findObjectRecord();
assertNotNull(rec); assertNotNull(rec);
assertEquals(32, rec.field_1_stream_id_offset); assertEquals(32, rec.field_1_stream_id_offset);
assertEquals(0, rec.field_6_stream_id); // WRONG! assertEquals(0, rec.field_6_stream_id); // WRONG!
assertEquals("Forms.CheckBox.1", rec.field_5_ole_classname); assertEquals("Forms.CheckBox.1", rec.field_5_ole_classname);
assertEquals(12, rec.remainingBytes.length); assertEquals(12, rec.remainingBytes.length);
// Doesn't have a directory // Doesn't have a directory
assertFalse(obj.hasDirectoryEntry()); assertFalse(obj.hasDirectoryEntry());
assertNotNull(obj.getObjectData()); assertNotNull(obj.getObjectData());
assertEquals(12, obj.getObjectData().length); assertEquals(12, obj.getObjectData().length);
assertEquals("Forms.CheckBox.1", obj.getOLE2ClassName()); assertEquals("Forms.CheckBox.1", obj.getOLE2ClassName());
try { try {
obj.getDirectory(); obj.getDirectory();
fail(); fail();
} catch(FileNotFoundException e) { } catch(FileNotFoundException e) {
// expectd during successful test // expectd during successful test
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
/** /**
* Test that we can delete sheets without * Test that we can delete sheets without
* breaking the build in named ranges * breaking the build in named ranges
@ -1011,73 +1011,73 @@ public final class TestBugs extends TestCase {
HSSFWorkbook wb = openSample("30978-alt.xls"); HSSFWorkbook wb = openSample("30978-alt.xls");
assertEquals(1, wb.getNumberOfNames()); assertEquals(1, wb.getNumberOfNames());
assertEquals(3, wb.getNumberOfSheets()); assertEquals(3, wb.getNumberOfSheets());
// Check all names fit within range, and use // Check all names fit within range, and use
// DeletedArea3DPtg // DeletedArea3DPtg
Workbook w = wb.getWorkbook(); Workbook w = wb.getWorkbook();
for(int i=0; i<w.getNumNames(); i++) { for(int i=0; i<w.getNumNames(); i++) {
NameRecord r = w.getNameRecord(i); NameRecord r = w.getNameRecord(i);
assertTrue(r.getSheetNumber() <= wb.getNumberOfSheets()); assertTrue(r.getSheetNumber() <= wb.getNumberOfSheets());
Ptg[] nd = r.getNameDefinition(); Ptg[] nd = r.getNameDefinition();
assertEquals(1, nd.length); assertEquals(1, nd.length);
assertTrue(nd[0] instanceof DeletedArea3DPtg); assertTrue(nd[0] instanceof DeletedArea3DPtg);
} }
// Delete the 2nd sheet // Delete the 2nd sheet
wb.removeSheetAt(1); wb.removeSheetAt(1);
// Re-check // Re-check
assertEquals(1, wb.getNumberOfNames()); assertEquals(1, wb.getNumberOfNames());
assertEquals(2, wb.getNumberOfSheets()); assertEquals(2, wb.getNumberOfSheets());
for(int i=0; i<w.getNumNames(); i++) { for(int i=0; i<w.getNumNames(); i++) {
NameRecord r = w.getNameRecord(i); NameRecord r = w.getNameRecord(i);
assertTrue(r.getSheetNumber() <= wb.getNumberOfSheets()); assertTrue(r.getSheetNumber() <= wb.getNumberOfSheets());
Ptg[] nd = r.getNameDefinition(); Ptg[] nd = r.getNameDefinition();
assertEquals(1, nd.length); assertEquals(1, nd.length);
assertTrue(nd[0] instanceof DeletedArea3DPtg); assertTrue(nd[0] instanceof DeletedArea3DPtg);
} }
// Save and re-load // Save and re-load
wb = writeOutAndReadBack(wb); wb = writeOutAndReadBack(wb);
w = wb.getWorkbook(); w = wb.getWorkbook();
assertEquals(1, wb.getNumberOfNames()); assertEquals(1, wb.getNumberOfNames());
assertEquals(2, wb.getNumberOfSheets()); assertEquals(2, wb.getNumberOfSheets());
for(int i=0; i<w.getNumNames(); i++) { for(int i=0; i<w.getNumNames(); i++) {
NameRecord r = w.getNameRecord(i); NameRecord r = w.getNameRecord(i);
assertTrue(r.getSheetNumber() <= wb.getNumberOfSheets()); assertTrue(r.getSheetNumber() <= wb.getNumberOfSheets());
Ptg[] nd = r.getNameDefinition(); Ptg[] nd = r.getNameDefinition();
assertEquals(1, nd.length); assertEquals(1, nd.length);
assertTrue(nd[0] instanceof DeletedArea3DPtg); assertTrue(nd[0] instanceof DeletedArea3DPtg);
} }
} }
/** /**
* Test that fonts get added properly * Test that fonts get added properly
*/ */
public void test45338() { public void test45338() {
HSSFWorkbook wb = new HSSFWorkbook(); HSSFWorkbook wb = new HSSFWorkbook();
assertEquals(4, wb.getNumberOfFonts()); assertEquals(4, wb.getNumberOfFonts());
HSSFSheet s = wb.createSheet(); HSSFSheet s = wb.createSheet();
s.createRow(0); s.createRow(0);
s.createRow(1); s.createRow(1);
HSSFCell c1 = s.getRow(0).createCell(0); HSSFCell c1 = s.getRow(0).createCell(0);
HSSFCell c2 = s.getRow(1).createCell(0); HSSFCell c2 = s.getRow(1).createCell(0);
assertEquals(4, wb.getNumberOfFonts()); assertEquals(4, wb.getNumberOfFonts());
HSSFFont f1 = wb.getFontAt((short)0); HSSFFont f1 = wb.getFontAt((short)0);
assertEquals(400, f1.getBoldweight()); assertEquals(400, f1.getBoldweight());
// Check that asking for the same font // Check that asking for the same font
// multiple times gives you the same thing. // multiple times gives you the same thing.
// Otherwise, our tests wouldn't work! // Otherwise, our tests wouldn't work!
@ -1094,22 +1094,22 @@ public final class TestBugs extends TestCase {
!= !=
wb.getFontAt((short)2) wb.getFontAt((short)2)
); );
// Look for a new font we have // Look for a new font we have
// yet to add // yet to add
assertNull( assertNull(
wb.findFont( wb.findFont(
(short)11, (short)123, (short)22, (short)11, (short)123, (short)22,
"Thingy", false, true, (short)2, (byte)2 "Thingy", false, true, (short)2, (byte)2
) )
); );
HSSFFont nf = wb.createFont(); HSSFFont nf = wb.createFont();
assertEquals(5, wb.getNumberOfFonts()); assertEquals(5, wb.getNumberOfFonts());
assertEquals(5, nf.getIndex()); assertEquals(5, nf.getIndex());
assertEquals(nf, wb.getFontAt((short)5)); assertEquals(nf, wb.getFontAt((short)5));
nf.setBoldweight((short)11); nf.setBoldweight((short)11);
nf.setColor((short)123); nf.setColor((short)123);
nf.setFontHeight((short)22); nf.setFontHeight((short)22);
@ -1118,32 +1118,32 @@ public final class TestBugs extends TestCase {
nf.setStrikeout(true); nf.setStrikeout(true);
nf.setTypeOffset((short)2); nf.setTypeOffset((short)2);
nf.setUnderline((byte)2); nf.setUnderline((byte)2);
assertEquals(5, wb.getNumberOfFonts()); assertEquals(5, wb.getNumberOfFonts());
assertEquals(nf, wb.getFontAt((short)5)); assertEquals(nf, wb.getFontAt((short)5));
// Find it now // Find it now
assertNotNull( assertNotNull(
wb.findFont( wb.findFont(
(short)11, (short)123, (short)22, (short)11, (short)123, (short)22,
"Thingy", false, true, (short)2, (byte)2 "Thingy", false, true, (short)2, (byte)2
) )
); );
assertEquals( assertEquals(
5, 5,
wb.findFont( wb.findFont(
(short)11, (short)123, (short)22, (short)11, (short)123, (short)22,
"Thingy", false, true, (short)2, (byte)2 "Thingy", false, true, (short)2, (byte)2
).getIndex() ).getIndex()
); );
assertEquals(nf, assertEquals(nf,
wb.findFont( wb.findFont(
(short)11, (short)123, (short)22, (short)11, (short)123, (short)22,
"Thingy", false, true, (short)2, (byte)2 "Thingy", false, true, (short)2, (byte)2
) )
); );
} }
/** /**
* From the mailing list - ensure we can handle a formula * From the mailing list - ensure we can handle a formula
* containing a zip code, eg ="70164" * containing a zip code, eg ="70164"
@ -1160,63 +1160,59 @@ public final class TestBugs extends TestCase {
c1.setCellFormula("70164"); c1.setCellFormula("70164");
c2.setCellFormula("\"70164\""); c2.setCellFormula("\"70164\"");
c3.setCellFormula("\"90210\""); c3.setCellFormula("\"90210\"");
// Check the formulas // Check the formulas
assertEquals("70164.0", c1.getCellFormula()); assertEquals("70164.0", c1.getCellFormula());
assertEquals("\"70164\"", c2.getCellFormula()); assertEquals("\"70164\"", c2.getCellFormula());
// And check the values - blank // And check the values - blank
assertEquals(0.0, c1.getNumericCellValue(), 0.00001); confirmCachedValue(0.0, c1);
assertEquals("", c1.getRichStringCellValue().getString()); confirmCachedValue(0.0, c2);
assertEquals(0.0, c2.getNumericCellValue(), 0.00001); confirmCachedValue(0.0, c3);
assertEquals("", c2.getRichStringCellValue().getString());
assertEquals(0.0, c3.getNumericCellValue(), 0.00001);
assertEquals("", c3.getRichStringCellValue().getString());
// Try changing the cached value on one of the string // Try changing the cached value on one of the string
// formula cells, so we can see it updates properly // formula cells, so we can see it updates properly
c3.setCellValue(new HSSFRichTextString("test")); c3.setCellValue(new HSSFRichTextString("test"));
assertEquals(0.0, c3.getNumericCellValue(), 0.00001); confirmCachedValue("test", c3);
assertEquals("test", c3.getRichStringCellValue().getString()); try {
c3.getNumericCellValue();
throw new AssertionFailedError("exception should have been thrown");
} catch (IllegalStateException e) {
assertEquals("Cannot get a numeric value from a text formula cell", e.getMessage());
}
// Now evaluate, they should all be changed // Now evaluate, they should all be changed
HSSFFormulaEvaluator eval = new HSSFFormulaEvaluator(s, wb); HSSFFormulaEvaluator eval = new HSSFFormulaEvaluator(s, wb);
eval.evaluateFormulaCell(c1); eval.evaluateFormulaCell(c1);
eval.evaluateFormulaCell(c2); eval.evaluateFormulaCell(c2);
eval.evaluateFormulaCell(c3); eval.evaluateFormulaCell(c3);
// Check that the cells now contain // Check that the cells now contain
// the correct values // the correct values
assertEquals(70164.0, c1.getNumericCellValue(), 0.00001); confirmCachedValue(70164.0, c1);
assertEquals("", c1.getRichStringCellValue().getString()); confirmCachedValue("70164", c2);
assertEquals(0.0, c2.getNumericCellValue(), 0.00001); confirmCachedValue("90210", c3);
assertEquals("70164", c2.getRichStringCellValue().getString());
assertEquals(0.0, c3.getNumericCellValue(), 0.00001);
assertEquals("90210", c3.getRichStringCellValue().getString());
// Write and read // Write and read
HSSFWorkbook nwb = writeOutAndReadBack(wb); HSSFWorkbook nwb = writeOutAndReadBack(wb);
HSSFSheet ns = nwb.getSheetAt(0); HSSFSheet ns = nwb.getSheetAt(0);
HSSFCell nc1 = ns.getRow(0).getCell(0); HSSFCell nc1 = ns.getRow(0).getCell(0);
HSSFCell nc2 = ns.getRow(0).getCell(1); HSSFCell nc2 = ns.getRow(0).getCell(1);
HSSFCell nc3 = ns.getRow(0).getCell(2); HSSFCell nc3 = ns.getRow(0).getCell(2);
// Re-check // Re-check
assertEquals(70164.0, nc1.getNumericCellValue(), 0.00001); confirmCachedValue(70164.0, nc1);
assertEquals("", nc1.getRichStringCellValue().getString()); confirmCachedValue("70164", nc2);
assertEquals(0.0, nc2.getNumericCellValue(), 0.00001); confirmCachedValue("90210", nc3);
assertEquals("70164", nc2.getRichStringCellValue().getString());
assertEquals(0.0, nc3.getNumericCellValue(), 0.00001);
assertEquals("90210", nc3.getRichStringCellValue().getString());
CellValueRecordInterface[] cvrs = ns.getSheet().getValueRecords(); CellValueRecordInterface[] cvrs = ns.getSheet().getValueRecords();
for (int i = 0; i < cvrs.length; i++) { for (int i = 0; i < cvrs.length; i++) {
CellValueRecordInterface cvr = cvrs[i]; CellValueRecordInterface cvr = cvrs[i];
if(cvr instanceof FormulaRecordAggregate) { if(cvr instanceof FormulaRecordAggregate) {
FormulaRecordAggregate fr = (FormulaRecordAggregate)cvr; FormulaRecordAggregate fr = (FormulaRecordAggregate)cvr;
if(i == 0) { if(i == 0) {
assertEquals(70164.0, fr.getFormulaRecord().getValue(), 0.0001); assertEquals(70164.0, fr.getFormulaRecord().getValue(), 0.0001);
assertNull(fr.getStringRecord()); assertNull(fr.getStringRecord());
@ -1233,7 +1229,18 @@ public final class TestBugs extends TestCase {
} }
assertEquals(3, cvrs.length); assertEquals(3, cvrs.length);
} }
private static void confirmCachedValue(double expectedValue, HSSFCell cell) {
assertEquals(HSSFCell.CELL_TYPE_FORMULA, cell.getCellType());
assertEquals(HSSFCell.CELL_TYPE_NUMERIC, cell.getCachedFormulaResultType());
assertEquals(expectedValue, cell.getNumericCellValue(), 0.0);
}
private static void confirmCachedValue(String expectedValue, HSSFCell cell) {
assertEquals(HSSFCell.CELL_TYPE_FORMULA, cell.getCellType());
assertEquals(HSSFCell.CELL_TYPE_STRING, cell.getCachedFormulaResultType());
assertEquals(expectedValue, cell.getRichStringCellValue().getString());
}
/** /**
* Problem with "Vector Rows", eg a whole * Problem with "Vector Rows", eg a whole
* column which is set to the result of * column which is set to the result of
@ -1242,37 +1249,37 @@ public final class TestBugs extends TestCase {
* {=sin(B1:B9){9,1)[rownum][0] * {=sin(B1:B9){9,1)[rownum][0]
* In this sample file, the vector column * In this sample file, the vector column
* is C, and the data column is B. * is C, and the data column is B.
* *
* For now, blows up with an exception from ExtPtg * For now, blows up with an exception from ExtPtg
* Expected ExpPtg to be converted from Shared to Non-Shared... * Expected ExpPtg to be converted from Shared to Non-Shared...
*/ */
public void DISABLEDtest43623() { public void DISABLEDtest43623() {
HSSFWorkbook wb = openSample("43623.xls"); HSSFWorkbook wb = openSample("43623.xls");
assertEquals(1, wb.getNumberOfSheets()); assertEquals(1, wb.getNumberOfSheets());
HSSFSheet s1 = wb.getSheetAt(0); HSSFSheet s1 = wb.getSheetAt(0);
HSSFCell c1 = s1.getRow(0).getCell(2); HSSFCell c1 = s1.getRow(0).getCell(2);
HSSFCell c2 = s1.getRow(1).getCell(2); HSSFCell c2 = s1.getRow(1).getCell(2);
HSSFCell c3 = s1.getRow(2).getCell(2); HSSFCell c3 = s1.getRow(2).getCell(2);
// These formula contents are a guess... // These formula contents are a guess...
assertEquals("{=sin(B1:B9){9,1)[0][0]", c1.getCellFormula()); assertEquals("{=sin(B1:B9){9,1)[0][0]", c1.getCellFormula());
assertEquals("{=sin(B1:B9){9,1)[1][0]", c2.getCellFormula()); assertEquals("{=sin(B1:B9){9,1)[1][0]", c2.getCellFormula());
assertEquals("{=sin(B1:B9){9,1)[2][0]", c3.getCellFormula()); assertEquals("{=sin(B1:B9){9,1)[2][0]", c3.getCellFormula());
// Save and re-open, ensure it still works // Save and re-open, ensure it still works
HSSFWorkbook nwb = writeOutAndReadBack(wb); HSSFWorkbook nwb = writeOutAndReadBack(wb);
HSSFSheet ns1 = nwb.getSheetAt(0); HSSFSheet ns1 = nwb.getSheetAt(0);
HSSFCell nc1 = ns1.getRow(0).getCell(2); HSSFCell nc1 = ns1.getRow(0).getCell(2);
HSSFCell nc2 = ns1.getRow(1).getCell(2); HSSFCell nc2 = ns1.getRow(1).getCell(2);
HSSFCell nc3 = ns1.getRow(2).getCell(2); HSSFCell nc3 = ns1.getRow(2).getCell(2);
assertEquals("{=sin(B1:B9){9,1)[0][0]", nc1.getCellFormula()); assertEquals("{=sin(B1:B9){9,1)[0][0]", nc1.getCellFormula());
assertEquals("{=sin(B1:B9){9,1)[1][0]", nc2.getCellFormula()); assertEquals("{=sin(B1:B9){9,1)[1][0]", nc2.getCellFormula());
assertEquals("{=sin(B1:B9){9,1)[2][0]", nc3.getCellFormula()); assertEquals("{=sin(B1:B9){9,1)[2][0]", nc3.getCellFormula());
} }
/** /**
* People are all getting confused about the last * People are all getting confused about the last
* row and cell number * row and cell number
@ -1280,48 +1287,48 @@ public final class TestBugs extends TestCase {
public void test30635() { public void test30635() {
HSSFWorkbook wb = new HSSFWorkbook(); HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet s = wb.createSheet(); HSSFSheet s = wb.createSheet();
// No rows, everything is 0 // No rows, everything is 0
assertEquals(0, s.getFirstRowNum()); assertEquals(0, s.getFirstRowNum());
assertEquals(0, s.getLastRowNum()); assertEquals(0, s.getLastRowNum());
assertEquals(0, s.getPhysicalNumberOfRows()); assertEquals(0, s.getPhysicalNumberOfRows());
// One row, most things are 0, physical is 1 // One row, most things are 0, physical is 1
s.createRow(0); s.createRow(0);
assertEquals(0, s.getFirstRowNum()); assertEquals(0, s.getFirstRowNum());
assertEquals(0, s.getLastRowNum()); assertEquals(0, s.getLastRowNum());
assertEquals(1, s.getPhysicalNumberOfRows()); assertEquals(1, s.getPhysicalNumberOfRows());
// And another, things change // And another, things change
s.createRow(4); s.createRow(4);
assertEquals(0, s.getFirstRowNum()); assertEquals(0, s.getFirstRowNum());
assertEquals(4, s.getLastRowNum()); assertEquals(4, s.getLastRowNum());
assertEquals(2, s.getPhysicalNumberOfRows()); assertEquals(2, s.getPhysicalNumberOfRows());
// Now start on cells // Now start on cells
HSSFRow r = s.getRow(0); HSSFRow r = s.getRow(0);
assertEquals(-1, r.getFirstCellNum()); assertEquals(-1, r.getFirstCellNum());
assertEquals(-1, r.getLastCellNum()); assertEquals(-1, r.getLastCellNum());
assertEquals(0, r.getPhysicalNumberOfCells()); assertEquals(0, r.getPhysicalNumberOfCells());
// Add a cell, things move off -1 // Add a cell, things move off -1
r.createCell(0); r.createCell(0);
assertEquals(0, r.getFirstCellNum()); assertEquals(0, r.getFirstCellNum());
assertEquals(1, r.getLastCellNum()); // last cell # + 1 assertEquals(1, r.getLastCellNum()); // last cell # + 1
assertEquals(1, r.getPhysicalNumberOfCells()); assertEquals(1, r.getPhysicalNumberOfCells());
r.createCell(1); r.createCell(1);
assertEquals(0, r.getFirstCellNum()); assertEquals(0, r.getFirstCellNum());
assertEquals(2, r.getLastCellNum()); // last cell # + 1 assertEquals(2, r.getLastCellNum()); // last cell # + 1
assertEquals(2, r.getPhysicalNumberOfCells()); assertEquals(2, r.getPhysicalNumberOfCells());
r.createCell(4); r.createCell(4);
assertEquals(0, r.getFirstCellNum()); assertEquals(0, r.getFirstCellNum());
assertEquals(5, r.getLastCellNum()); // last cell # + 1 assertEquals(5, r.getLastCellNum()); // last cell # + 1
assertEquals(3, r.getPhysicalNumberOfCells()); assertEquals(3, r.getPhysicalNumberOfCells());
} }
/** /**
* Data Tables - ptg 0x2 * Data Tables - ptg 0x2
*/ */
@ -1330,25 +1337,25 @@ public final class TestBugs extends TestCase {
HSSFSheet s; HSSFSheet s;
HSSFRow r; HSSFRow r;
HSSFCell c; HSSFCell c;
// Check the contents of the formulas // Check the contents of the formulas
// E4 to G9 of sheet 4 make up the table // E4 to G9 of sheet 4 make up the table
s = wb.getSheet("OneVariable Table Completed"); s = wb.getSheet("OneVariable Table Completed");
r = s.getRow(3); r = s.getRow(3);
c = r.getCell(4); c = r.getCell(4);
assertEquals(HSSFCell.CELL_TYPE_FORMULA, c.getCellType()); assertEquals(HSSFCell.CELL_TYPE_FORMULA, c.getCellType());
// TODO - check the formula once tables and // TODO - check the formula once tables and
// arrays are properly supported // arrays are properly supported
// E4 to H9 of sheet 5 make up the table // E4 to H9 of sheet 5 make up the table
s = wb.getSheet("TwoVariable Table Example"); s = wb.getSheet("TwoVariable Table Example");
r = s.getRow(3); r = s.getRow(3);
c = r.getCell(4); c = r.getCell(4);
assertEquals(HSSFCell.CELL_TYPE_FORMULA, c.getCellType()); assertEquals(HSSFCell.CELL_TYPE_FORMULA, c.getCellType());
// TODO - check the formula once tables and // TODO - check the formula once tables and
// arrays are properly supported // arrays are properly supported
} }
@ -1361,7 +1368,7 @@ public final class TestBugs extends TestCase {
HSSFSheet sh = wb.getSheetAt(0); HSSFSheet sh = wb.getSheetAt(0);
for(short i=0; i < 30; i++) sh.autoSizeColumn(i); for(short i=0; i < 30; i++) sh.autoSizeColumn(i);
} }
/** /**
* We used to add too many UncalcRecords to sheets * We used to add too many UncalcRecords to sheets
* with diagrams on. Don't any more * with diagrams on. Don't any more
@ -1371,41 +1378,41 @@ public final class TestBugs extends TestCase {
wb.getSheetAt(0).setForceFormulaRecalculation(true); wb.getSheetAt(0).setForceFormulaRecalculation(true);
wb.getSheetAt(1).setForceFormulaRecalculation(false); wb.getSheetAt(1).setForceFormulaRecalculation(false);
wb.getSheetAt(2).setForceFormulaRecalculation(true); wb.getSheetAt(2).setForceFormulaRecalculation(true);
// Write out and back in again // Write out and back in again
// This used to break // This used to break
HSSFWorkbook nwb = writeOutAndReadBack(wb); HSSFWorkbook nwb = writeOutAndReadBack(wb);
// Check now set as it should be // Check now set as it should be
assertTrue(nwb.getSheetAt(0).getForceFormulaRecalculation()); assertTrue(nwb.getSheetAt(0).getForceFormulaRecalculation());
assertFalse(nwb.getSheetAt(1).getForceFormulaRecalculation()); assertFalse(nwb.getSheetAt(1).getForceFormulaRecalculation());
assertTrue(nwb.getSheetAt(2).getForceFormulaRecalculation()); assertTrue(nwb.getSheetAt(2).getForceFormulaRecalculation());
} }
/** /**
* Very hidden sheets not displaying as such * Very hidden sheets not displaying as such
*/ */
public void test45761() { public void test45761() {
HSSFWorkbook wb = openSample("45761.xls"); HSSFWorkbook wb = openSample("45761.xls");
assertEquals(3, wb.getNumberOfSheets()); assertEquals(3, wb.getNumberOfSheets());
assertFalse(wb.isSheetHidden(0)); assertFalse(wb.isSheetHidden(0));
assertFalse(wb.isSheetVeryHidden(0)); assertFalse(wb.isSheetVeryHidden(0));
assertTrue(wb.isSheetHidden(1)); assertTrue(wb.isSheetHidden(1));
assertFalse(wb.isSheetVeryHidden(1)); assertFalse(wb.isSheetVeryHidden(1));
assertFalse(wb.isSheetHidden(2)); assertFalse(wb.isSheetHidden(2));
assertTrue(wb.isSheetVeryHidden(2)); assertTrue(wb.isSheetVeryHidden(2));
// Change 0 to be very hidden, and re-load // Change 0 to be very hidden, and re-load
wb.setSheetHidden(0, 2); wb.setSheetHidden(0, 2);
HSSFWorkbook nwb = writeOutAndReadBack(wb); HSSFWorkbook nwb = writeOutAndReadBack(wb);
assertFalse(nwb.isSheetHidden(0)); assertFalse(nwb.isSheetHidden(0));
assertTrue(nwb.isSheetVeryHidden(0)); assertTrue(nwb.isSheetVeryHidden(0));
assertTrue(nwb.isSheetHidden(1)); assertTrue(nwb.isSheetHidden(1));
assertFalse(nwb.isSheetVeryHidden(1)); assertFalse(nwb.isSheetVeryHidden(1));
assertFalse(nwb.isSheetHidden(2)); assertFalse(nwb.isSheetHidden(2));
assertTrue(nwb.isSheetVeryHidden(2)); assertTrue(nwb.isSheetVeryHidden(2));
} }
} }