<!-- Don't forget to update status.xml too! -->
<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">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>
<!-- Don't forget to update changes.xml too! -->
<changes>
<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">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>
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
+
package org.apache.poi.hssf.extractor;
import java.io.IOException;
/**
* A text extractor for Excel files, that is based
* 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
* 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
* intended for display to the user.
* To turn an excel file into a CSV or similar, then see
private POIFSFileSystem fs;
private boolean includeSheetNames = true;
private boolean formulasNotResults = false;
-
- public EventBasedExcelExtractor(POIFSFileSystem fs) throws IOException {
+
+ public EventBasedExcelExtractor(POIFSFileSystem fs) {
super(null);
this.fs = fs;
}
public void setFormulasNotResults(boolean formulasNotResults) {
this.formulasNotResults = formulasNotResults;
}
-
-
+
+
/**
* Retreives the text contents of the file
*/
String text = null;
try {
TextListener tl = triggerExtraction();
-
+
text = tl.text.toString();
if(! text.endsWith("\n")) {
text = text + "\n";
} catch(IOException e) {
throw new RuntimeException(e);
}
-
+
return text;
}
-
+
private TextListener triggerExtraction() throws IOException {
TextListener tl = new TextListener();
FormatTrackingHSSFListener ft = new FormatTrackingHSSFListener(tl);
tl.ft = ft;
-
+
// Register and process
HSSFEventFactory factory = new HSSFEventFactory();
HSSFRequest request = new HSSFRequest();
request.addListenerForAllRecords(ft);
-
+
factory.processWorkbookEvents(request, fs);
-
+
return tl;
}
-
+
private class TextListener implements HSSFListener {
private FormatTrackingHSSFListener ft;
private SSTRecord sstRecord;
-
+
private List sheetNames = new ArrayList();
private StringBuffer text = new StringBuffer();
private int sheetNum = -1;
private int rowNum;
-
+
private boolean outputNextStringValue = false;
private int nextRow = -1;
-
+
public void processRecord(Record record) {
String thisText = null;
int thisRow = -1;
if(bof.getType() == BOFRecord.TYPE_WORKSHEET) {
sheetNum++;
rowNum = -1;
-
+
if(includeSheetNames) {
if(text.length() > 0) text.append("\n");
text.append(sheetNames.get(sheetNum));
case SSTRecord.sid:
sstRecord = (SSTRecord)record;
break;
-
- case FormulaRecord.sid:
- FormulaRecord frec = (FormulaRecord) record;
- thisRow = frec.getRow();
-
- if(formulasNotResults) {
- thisText = FormulaParser.toFormulaString(null, frec.getParsedExpression());
- } else {
- if(Double.isNaN( frec.getValue() )) {
- // Formula result is a string
- // This is stored in the next record
- outputNextStringValue = true;
- nextRow = frec.getRow();
- } else {
- thisText = formatNumberDateCell(frec, frec.getValue());
- }
- }
- break;
- case StringRecord.sid:
- if(outputNextStringValue) {
- // String for formula
- StringRecord srec = (StringRecord)record;
- thisText = srec.getString();
- thisRow = nextRow;
- outputNextStringValue = false;
- }
- break;
- case LabelRecord.sid:
- LabelRecord lrec = (LabelRecord) record;
- thisRow = lrec.getRow();
- thisText = lrec.getValue();
- break;
- case LabelSSTRecord.sid:
- LabelSSTRecord lsrec = (LabelSSTRecord) record;
- thisRow = lsrec.getRow();
- if(sstRecord == null) {
- throw new IllegalStateException("No SST record found");
- }
- thisText = sstRecord.getString(lsrec.getSSTIndex()).toString();
- break;
- case NoteRecord.sid:
- NoteRecord nrec = (NoteRecord) record;
- thisRow = nrec.getRow();
- // TODO: Find object to match nrec.getShapeId()
- break;
- case NumberRecord.sid:
- NumberRecord numrec = (NumberRecord) record;
- thisRow = numrec.getRow();
- thisText = formatNumberDateCell(numrec, numrec.getValue());
- break;
- default:
- break;
+
+ case FormulaRecord.sid:
+ FormulaRecord frec = (FormulaRecord) record;
+ thisRow = frec.getRow();
+
+ if(formulasNotResults) {
+ thisText = FormulaParser.toFormulaString(null, frec.getParsedExpression());
+ } else {
+ if(frec.hasCachedResultString()) {
+ // Formula result is a string
+ // This is stored in the next record
+ outputNextStringValue = true;
+ nextRow = frec.getRow();
+ } else {
+ thisText = formatNumberDateCell(frec, frec.getValue());
+ }
+ }
+ break;
+ case StringRecord.sid:
+ if(outputNextStringValue) {
+ // String for formula
+ StringRecord srec = (StringRecord)record;
+ thisText = srec.getString();
+ thisRow = nextRow;
+ outputNextStringValue = false;
+ }
+ break;
+ case LabelRecord.sid:
+ LabelRecord lrec = (LabelRecord) record;
+ thisRow = lrec.getRow();
+ thisText = lrec.getValue();
+ break;
+ case LabelSSTRecord.sid:
+ LabelSSTRecord lsrec = (LabelSSTRecord) record;
+ thisRow = lsrec.getRow();
+ if(sstRecord == null) {
+ throw new IllegalStateException("No SST record found");
+ }
+ thisText = sstRecord.getString(lsrec.getSSTIndex()).toString();
+ break;
+ case NoteRecord.sid:
+ NoteRecord nrec = (NoteRecord) record;
+ thisRow = nrec.getRow();
+ // TODO: Find object to match nrec.getShapeId()
+ break;
+ case NumberRecord.sid:
+ NumberRecord numrec = (NumberRecord) record;
+ thisRow = numrec.getRow();
+ thisText = formatNumberDateCell(numrec, numrec.getValue());
+ break;
+ default:
+ break;
}
-
+
if(thisText != null) {
if(thisRow != rowNum) {
rowNum = thisRow;
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
*/
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);
String formatString = ft.getFormatString(cell);
-
+
if(formatString == null) {
- return Double.toString(value);
- } else {
- // Is it a date?
- if(HSSFDateUtil.isADateFormat(formatIndex,formatString) &&
- HSSFDateUtil.isValidExcelDate(value)) {
- // Java wants M not m for month
- formatString = formatString.replace('m','M');
- // Change \- into -, if it's there
- formatString = formatString.replaceAll("\\\\-","-");
-
- // Format as a date
- Date d = HSSFDateUtil.getJavaDate(value, false);
- DateFormat df = new SimpleDateFormat(formatString);
- return df.format(d);
- } else {
- if(formatString == "General") {
- // Some sort of wierd default
- return Double.toString(value);
- }
-
- // Format as a number
- DecimalFormat df = new DecimalFormat(formatString);
- return df.format(value);
- }
- }
+ return Double.toString(value);
+ } else {
+ // Is it a date?
+ if(HSSFDateUtil.isADateFormat(formatIndex,formatString) &&
+ HSSFDateUtil.isValidExcelDate(value)) {
+ // Java wants M not m for month
+ formatString = formatString.replace('m','M');
+ // Change \- into -, if it's there
+ formatString = formatString.replaceAll("\\\\-","-");
+
+ // Format as a date
+ Date d = HSSFDateUtil.getJavaDate(value, false);
+ DateFormat df = new SimpleDateFormat(formatString);
+ return df.format(d);
+ } else {
+ if(formatString == "General") {
+ // Some sort of wierd default
+ return Double.toString(value);
+ }
+
+ // Format as a number
+ DecimalFormat df = new DecimalFormat(formatString);
+ return df.format(value);
+ }
+ }
}
}
}
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
+
package org.apache.poi.hssf.extractor;
import java.io.IOException;
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.HSSFCell;
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.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
int lastCell = row.getLastCellNum();
for(int k=firstCell;k<lastCell;k++) {
HSSFCell cell = row.getCell(k);
- boolean outputContents = false;
if(cell == null) { continue; }
+ boolean outputContents = true;
switch(cell.getCellType()) {
+ case HSSFCell.CELL_TYPE_BLANK:
+ outputContents = false;
+ break;
case HSSFCell.CELL_TYPE_STRING:
text.append(cell.getRichStringCellValue().getString());
- outputContents = true;
break;
case HSSFCell.CELL_TYPE_NUMERIC:
// Note - we don't apply any formatting!
text.append(cell.getNumericCellValue());
- outputContents = true;
break;
case HSSFCell.CELL_TYPE_BOOLEAN:
text.append(cell.getBooleanCellValue());
- outputContents = true;
+ break;
+ case HSSFCell.CELL_TYPE_ERROR:
+ text.append(ErrorEval.getText(cell.getErrorCellValue()));
break;
case HSSFCell.CELL_TYPE_FORMULA:
if(formulasNotResults) {
text.append(cell.getCellFormula());
} else {
- // Try it as a string, if not as a number
- HSSFRichTextString str =
- cell.getRichStringCellValue();
- if(str != null && str.length() > 0) {
- text.append(str.toString());
- } else {
- // Try and treat it as a number
- double val = cell.getNumericCellValue();
- text.append(val);
+ switch(cell.getCachedFormulaResultType()) {
+ case HSSFCell.CELL_TYPE_STRING:
+ HSSFRichTextString str = cell.getRichStringCellValue();
+ if(str != null && str.length() > 0) {
+ text.append(str.toString());
+ }
+ break;
+ case HSSFCell.CELL_TYPE_NUMERIC:
+ 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;
+ default:
+ throw new RuntimeException("Unexpected cell type (" + cell.getCellType() + ")");
}
// Output the comment, if requested and exists
package org.apache.poi.hssf.record;
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.BitFieldFactory;
import org.apache.poi.util.HexDump;
*/
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)
- private static int FIXED_SIZE = 22;
-
- private static final BitField alwaysCalc = BitFieldFactory.getInstance(0x0001);
- private static final BitField calcOnLoad = BitFieldFactory.getInstance(0x0002);
- private static final BitField sharedFormula = BitFieldFactory.getInstance(0x0008);
-
- private int field_1_row;
- private short field_2_column;
- private short field_3_xf;
- private double field_4_value;
- private short field_5_options;
- private int field_6_zero;
- private Ptg[] field_8_parsed_expr;
-
- /**
- * Since the NaN support seems sketchy (different constants) we'll store and spit it out directly
- */
- private byte[] value_data;
-
- /** Creates new FormulaRecord */
-
- public FormulaRecord() {
- field_8_parsed_expr = Ptg.EMPTY_PTG_ARRAY;
- }
-
- /**
- * Constructs a Formula record and sets its fields appropriately.
- * Note - id must be 0x06 (NOT 0x406 see MSKB #Q184647 for an
- * "explanation of this bug in the documentation) or an exception
- * will be throw upon validation
- *
- * @param in the RecordInputstream to read the record from
- */
-
- public FormulaRecord(RecordInputStream in) {
- super(in);
- }
-
- 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();
- 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 void setRow(int row) {
- field_1_row = row;
- }
-
- public void setColumn(short column) {
- field_2_column = column;
- }
-
- public void setXFIndex(short xf) {
- field_3_xf = xf;
- }
-
- /**
- * set the calculated value of the formula
- *
- * @param value calculated value
- */
- public void setValue(double value) {
- field_4_value = value;
- }
-
- /**
- * set the option flags
- *
- * @param options bitmask
- */
- public void setOptions(short options) {
- field_5_options = options;
- }
-
- public int getRow() {
- return field_1_row;
- }
-
- public short getColumn() {
- return field_2_column;
- }
-
- public short getXFIndex() {
- return field_3_xf;
- }
-
- /**
- * get the calculated value of the formula
- *
- * @return calculated value
- */
- public double getValue() {
- return field_4_value;
- }
-
- /**
- * get the option flags
- *
- * @return bitmask
- */
- public short getOptions() {
- return field_5_options;
- }
-
- public boolean isSharedFormula() {
- return sharedFormula.isSet(field_5_options);
- }
- public void setSharedFormula(boolean flag) {
- field_5_options =
- sharedFormula.setShortBoolean(field_5_options, flag);
- }
-
- public boolean isAlwaysCalc() {
- return alwaysCalc.isSet(field_5_options);
- }
- public void setAlwaysCalc(boolean flag) {
- field_5_options =
- alwaysCalc.setShortBoolean(field_5_options, flag);
- }
-
- public boolean isCalcOnLoad() {
- return calcOnLoad.isSet(field_5_options);
- }
- public void setCalcOnLoad(boolean flag) {
- field_5_options =
- calcOnLoad.setShortBoolean(field_5_options, flag);
- }
-
- /**
- * @return the formula tokens. never <code>null</code>
- */
- public Ptg[] getParsedExpression() {
- return (Ptg[]) field_8_parsed_expr.clone();
- }
-
- public void setParsedExpression(Ptg[] ptgs) {
- field_8_parsed_expr = ptgs;
- }
-
- /**
- * 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;
- }
-
- private int getDataSize() {
- return FIXED_SIZE + Ptg.getEncodedSize(field_8_parsed_expr);
- }
- public int serialize(int offset, byte [] data) {
-
- int dataSize = getDataSize();
-
- LittleEndian.putShort(data, 0 + offset, sid);
- LittleEndian.putUShort(data, 2 + offset, dataSize);
- 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) {
- System.arraycopy(value_data,0,data,10 + offset,value_data.length);
- } else {
- LittleEndian.putDouble(data, 10 + offset, field_4_value);
- }
-
- 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 (Double.isNaN(this.getValue()) && value_data != null) {
- sb.append("(NaN)").append(HexDump.dump(value_data,0,0)).append("\n");
- } else {
- 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++ ) {
- 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.value_data = value_data;
- return rec;
- }
+ 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 final BitField alwaysCalc = BitFieldFactory.getInstance(0x0001);
+ private static final BitField calcOnLoad = BitFieldFactory.getInstance(0x0002);
+ private static final BitField sharedFormula = BitFieldFactory.getInstance(0x0008);
+
+ /**
+ * Manages the cached formula result values of other types besides numeric.
+ * Excel encodes the same 8 bytes that would be field_4_value with various NaN
+ * values that are decoded/encoded by this class.
+ */
+ private static final class SpecialCachedValue {
+ /** 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;
+ public static final int BOOLEAN = 1;
+ public static final int ERROR_CODE = 2;
+ public static final int EMPTY = 3;
+
+ private final byte[] _variableData;
+
+ private SpecialCachedValue(byte[] data) {
+ _variableData = data;
+ }
+ public int getTypeCode() {
+ return _variableData[0];
+ }
+
+ /**
+ * @return <code>null</code> if the double value encoded by <tt>valueLongBits</tt>
+ * is a normal (non NaN) double value.
+ */
+ public static SpecialCachedValue create(long valueLongBits) {
+ if ((BIT_MARKER & valueLongBits) != BIT_MARKER) {
+ return null;
+ }
+
+ byte[] result = new byte[VARIABLE_DATA_LENGTH];
+ 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();
+ }
+ }
+
+
+
+ private int field_1_row;
+ private short field_2_column;
+ private short field_3_xf;
+ private double field_4_value;
+ private short field_5_options;
+ private int field_6_zero;
+ private Ptg[] field_8_parsed_expr;
+
+ /**
+ * Since the NaN support seems sketchy (different constants) we'll store and spit it out directly
+ */
+ private SpecialCachedValue specialCachedValue;
+
+ /** Creates new FormulaRecord */
+
+ public FormulaRecord() {
+ field_8_parsed_expr = Ptg.EMPTY_PTG_ARRAY;
+ }
+
+ /**
+ * Constructs a Formula record and sets its fields appropriately.
+ * Note - id must be 0x06 (NOT 0x406 see MSKB #Q184647 for an
+ * "explanation of this bug in the documentation) or an exception
+ * will be throw upon validation
+ *
+ * @param in the RecordInputstream to read the record from
+ */
+
+ public FormulaRecord(RecordInputStream in) {
+ super(in);
+ }
+
+ protected void fillFields(RecordInputStream in) {
+ 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);
+ }
+
+ field_6_zero = in.readInt();
+ 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 void setRow(int row) {
+ field_1_row = row;
+ }
+
+ public void setColumn(short column) {
+ field_2_column = column;
+ }
+
+ public void setXFIndex(short xf) {
+ field_3_xf = xf;
+ }
+
+ /**
+ * set the calculated value of the formula
+ *
+ * @param value calculated value
+ */
+ public void setValue(double value) {
+ field_4_value = value;
+ specialCachedValue = null;
+ }
+
+ public void setCachedResultTypeEmptyString() {
+ specialCachedValue = SpecialCachedValue.createCachedEmptyValue();
+ }
+ public void setCachedResultTypeString() {
+ specialCachedValue = SpecialCachedValue.createForString();
+ }
+ 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() {
+ if (specialCachedValue == null) {
+ return HSSFCell.CELL_TYPE_NUMERIC;
+ }
+ return specialCachedValue.getValueType();
+ }
+
+ public boolean getCachedBooleanValue() {
+ return specialCachedValue.getBooleanValue();
+ }
+ public int getCachedErrorValue() {
+ return specialCachedValue.getErrorValue();
+ }
+
+
+ /**
+ * set the option flags
+ *
+ * @param options bitmask
+ */
+ public void setOptions(short options) {
+ field_5_options = options;
+ }
+
+ public int getRow() {
+ return field_1_row;
+ }
+
+ public short getColumn() {
+ return field_2_column;
+ }
+
+ public short getXFIndex() {
+ return field_3_xf;
+ }
+
+ /**
+ * get the calculated value of the formula
+ *
+ * @return calculated value
+ */
+ public double getValue() {
+ return field_4_value;
+ }
+
+ /**
+ * get the option flags
+ *
+ * @return bitmask
+ */
+ public short getOptions() {
+ return field_5_options;
+ }
+
+ public boolean isSharedFormula() {
+ return sharedFormula.isSet(field_5_options);
+ }
+ public void setSharedFormula(boolean flag) {
+ field_5_options =
+ sharedFormula.setShortBoolean(field_5_options, flag);
+ }
+
+ public boolean isAlwaysCalc() {
+ return alwaysCalc.isSet(field_5_options);
+ }
+ public void setAlwaysCalc(boolean flag) {
+ field_5_options =
+ alwaysCalc.setShortBoolean(field_5_options, flag);
+ }
+
+ public boolean isCalcOnLoad() {
+ return calcOnLoad.isSet(field_5_options);
+ }
+ public void setCalcOnLoad(boolean flag) {
+ field_5_options =
+ calcOnLoad.setShortBoolean(field_5_options, flag);
+ }
+
+ /**
+ * @return the formula tokens. never <code>null</code>
+ */
+ public Ptg[] getParsedExpression() {
+ return (Ptg[]) field_8_parsed_expr.clone();
+ }
+
+ public void setParsedExpression(Ptg[] ptgs) {
+ field_8_parsed_expr = ptgs;
+ }
+
+ /**
+ * 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;
+ }
+
+ private int getDataSize() {
+ return FIXED_SIZE + Ptg.getEncodedSize(field_8_parsed_expr);
+ }
+ public int serialize(int offset, byte [] data) {
+
+ int dataSize = getDataSize();
+
+ LittleEndian.putShort(data, 0 + offset, sid);
+ LittleEndian.putUShort(data, 2 + offset, dataSize);
+ LittleEndian.putUShort(data, 4 + offset, getRow());
+ LittleEndian.putShort(data, 6 + offset, getColumn());
+ LittleEndian.putShort(data, 8 + offset, getXFIndex());
+
+ if (specialCachedValue == null) {
+ LittleEndian.putDouble(data, 10 + offset, field_4_value);
+ } 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;
+ }
}
return result;
}
- byte[] NAN_data = null;
public double readDouble() {
- checkRecordPosition();
- //Reset NAN data
- NAN_data = null;
- 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.
+ checkRecordPosition();
+ long valueLongBits = LittleEndian.getLong(data, recordOffset);
+ double result = Double.longBitsToDouble(valueLongBits);
if (Double.isNaN(result)) {
- NAN_data = new byte[8];
- System.arraycopy(data, recordOffset, NAN_data, 0, 8);
+ throw new RuntimeException("Did not expect to read NaN");
}
-
recordOffset += LittleEndian.DOUBLE_SIZE;
pos += LittleEndian.DOUBLE_SIZE;
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() {
checkRecordPosition();
}
public String readCompressedUnicode(int length) {
- if(length == 0) {
- return "";
- }
if ((length < 0) || ((remaining() < length) && !isContinueNext())) {
throw new IllegalArgumentException("Illegal length " + length);
}
if(compressByte != 0) throw new IllegalArgumentException("compressByte in continue records must be 0 while reading compressed unicode");
}
byte b = readByte();
- //Typecast direct to char from byte with high bit set causes all ones
- //in the high byte of the char (which is of course incorrect)
- char ch = (char)( (short)0xff & (short)b );
+ char ch = (char)(0x00FF & b); // avoid sex
buf.append(ch);
}
return buf.toString();
import org.apache.poi.hssf.record.CellValueRecordInterface;
import org.apache.poi.hssf.record.FormulaRecord;
import org.apache.poi.hssf.record.Record;
+import org.apache.poi.hssf.record.RecordFormatException;
import org.apache.poi.hssf.record.StringRecord;
/**
private SharedValueManager _sharedValueManager;
/** caches the calculated result of the formula */
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.
* @param svm the {@link SharedValueManager} for the current sheet
*/
if (svm == 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()) {
svm.convertSharedFormulaRecord(formulaRec);
}
_stringRecord = stringRec;
}
- public void setStringRecord(StringRecord stringRecord) {
- _stringRecord = stringRecord;
- }
-
public FormulaRecord getFormulaRecord() {
return _formulaRecord;
}
+ /**
+ * debug only
+ * TODO - encapsulate
+ */
public StringRecord getStringRecord() {
return _stringRecord;
}
-
+
public short getXFIndex() {
return _formulaRecord.getXFIndex();
}
public String toString() {
return _formulaRecord.toString();
}
-
+
public void visitContainedRecords(RecordVisitor rv) {
rv.visitRecord(_formulaRecord);
Record sharedFormulaRecord = _sharedValueManager.getRecordForFirstCell(_formulaRecord);
rv.visitRecord(_stringRecord);
}
}
-
+
public String getStringValue() {
if(_stringRecord==null) {
return null;
}
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);
+ }
}
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
+import java.util.List;
import org.apache.poi.hssf.model.FormulaParser;
import org.apache.poi.hssf.model.Sheet;
* Cells can be numeric, formula-based or string-based (text). The cell type
* specifies this. String cells cannot conatin numbers and numeric cells cannot
* contain strings (at least according to our model). Client apps should do the
- * conversions themselves. Formula cells have the formula string, as well as
- * the formula result, which can be numeric or string.
+ * conversions themselves. Formula cells have the formula string, as well as
+ * the formula result, which can be numeric or string.
* <p>
* Cells should have their number (0 based) before being added to a row. Only
* cells that have values should be added.
public final static int CELL_TYPE_BOOLEAN = 4;
/** Error Cell type (5) @see #setCellType(int) @see #getCellType() */
public final static int CELL_TYPE_ERROR = 5;
-
+
public final static short ENCODING_UNCHANGED = -1;
public final static short ENCODING_COMPRESSED_UNICODE = 0;
public final static short ENCODING_UTF_16 = 1;
+
+ private final HSSFWorkbook book;
+ private final Sheet sheet;
private int cellType;
private HSSFRichTextString stringValue;
- private HSSFWorkbook book;
- private Sheet sheet;
private CellValueRecordInterface record;
private HSSFComment comment;
short xfindex = sheet.getXFIndexForColAt(col);
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
stringValue = null;
this.book = book;
this.sheet = sheet;
-
+
short xfindex = sheet.getXFIndexForColAt(col);
setCellType(type,false,row,col,xfindex);
}
* used internally -- given a cell value record, figure out its type
*/
private static int determineType(CellValueRecordInterface cval) {
- if (cval instanceof FormulaRecordAggregate) {
- return HSSFCell.CELL_TYPE_FORMULA;
- }
- // all others are plain BIFF records
+ if (cval instanceof FormulaRecordAggregate) {
+ return HSSFCell.CELL_TYPE_FORMULA;
+ }
+ // all others are plain BIFF records
Record record = ( Record ) cval;
switch (record.getSid()) {
}
throw new RuntimeException("Bad cell value rec (" + cval.getClass().getName() + ")");
}
-
+
/**
* Returns the Workbook that this Cell is bound to
* @return
*/
protected Workbook getBoundWorkbook() {
- return book.getWorkbook();
+ return book.getWorkbook();
}
/**
{
record.setColumn(num);
}
-
+
/**
* Updates the cell record's idea of what
* column it belongs in (0 based)
*/
protected void updateCellNum(short num)
{
- record.setColumn(num);
+ record.setColumn(num);
}
/**
errRec.setColumn(col);
if (setValue)
{
- errRec.setValue(getErrorCellValue());
+ errRec.setValue((byte)HSSFErrorConstants.ERROR_VALUE);
}
errRec.setXFIndex(styleIndex);
errRec.setRow(row);
record = errRec;
break;
}
- if (cellType != this.cellType &&
+ if (cellType != this.cellType &&
this.cellType!=-1 ) // Special Value to indicate an uninitialized Cell
{
sheet.replaceValueRecord(record);
* 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.
*/
- public void setCellValue(double value)
- {
+ public void setCellValue(double value) {
int row=record.getRow();
short col=record.getColumn();
short styleIndex=record.getXFIndex();
- if ((cellType != CELL_TYPE_NUMERIC) && (cellType != CELL_TYPE_FORMULA))
- {
- setCellType(CELL_TYPE_NUMERIC, false, row, col, styleIndex);
- }
-
- // Save into the appropriate record
- if(record instanceof FormulaRecordAggregate) {
- (( FormulaRecordAggregate ) record).getFormulaRecord().setValue(value);
- } else {
- (( NumberRecord ) record).setValue(value);
+
+ switch (cellType) {
+ default:
+ setCellType(CELL_TYPE_NUMERIC, false, row, col, styleIndex);
+ case CELL_TYPE_ERROR:
+ (( NumberRecord ) record).setValue(value);
+ break;
+ case CELL_TYPE_FORMULA:
+ ((FormulaRecordAggregate)record).getFormulaRecord().setValue(value);
+ break;
}
}
/**
* set a date value for the cell. Excel treats dates as numeric so you will need to format the cell as
* a date.
- *
+ *
* 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
* 20:00-03:00 will be reported as the same value (20:00) even that there
return;
}
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.
FormulaRecordAggregate fr = (FormulaRecordAggregate) record;
-
- // 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());
+ fr.setCachedStringResult(value.getString());
// Update our local cache to the un-formatted version
- stringValue = new HSSFRichTextString(sr.getString());
-
+ stringValue = new HSSFRichTextString(value.getString());
+
// All done
return;
}
// If we get here, we're not dealing with a formula,
// so handle things as a normal rich text cell
-
+
if (cellType != CELL_TYPE_STRING) {
setCellType(CELL_TYPE_STRING, false, row, col, styleIndex);
}
FormulaRecord frec = rec.getFormulaRecord();
frec.setOptions((short) 2);
frec.setValue(0);
-
+
//only set to default if there is no extended format index already set
if (rec.getXFIndex() == (short)0) {
- rec.setXFIndex((short) 0x0f);
- }
+ rec.setXFIndex((short) 0x0f);
+ }
Ptg[] ptgs = FormulaParser.parse(formula, book);
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() {
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 blank cells we return a 0.
* See {@link HSSFDataFormatter} for turning this
* number into a string similar to that which
- * Excel would render this number as.
+ * Excel would render this number as.
*/
- public double getNumericCellValue()
- {
- if (cellType == CELL_TYPE_BLANK)
- {
- return 0;
- }
- if (cellType == CELL_TYPE_STRING)
- {
- throw new NumberFormatException(
- "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();
+ public double getNumericCellValue() {
+
+ switch(cellType) {
+ case CELL_TYPE_BLANK:
+ return 0.0;
+ case CELL_TYPE_NUMERIC:
+ return ((NumberRecord)record).getValue();
+ default:
+ throw typeMismatch(CELL_TYPE_NUMERIC, cellType, false);
+ case CELL_TYPE_FORMULA:
+ break;
}
- throw new NumberFormatException("Unknown Record Type in Cell:"+cellType);
+ FormulaRecord fr = ((FormulaRecordAggregate)record).getFormulaRecord();
+ checkFormulaCachedValueType(CELL_TYPE_NUMERIC, fr);
+ return fr.getValue();
}
/**
- * 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 blank cells we return a null.
* See {@link HSSFDataFormatter} for formatting
* this date into a string similar to how excel does.
*/
- public Date getDateCellValue()
- {
- if (cellType == CELL_TYPE_BLANK)
- {
+ public Date getDateCellValue() {
+
+ if (cellType == CELL_TYPE_BLANK) {
return null;
}
- if (cellType == CELL_TYPE_STRING)
- {
- 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();
+ double value = getNumericCellValue();
if (book.getWorkbook().isUsing1904DateWindowing()) {
- return HSSFDateUtil.getJavaDate(value,true);
- }
- else {
- return HSSFDateUtil.getJavaDate(value,false);
+ return HSSFDateUtil.getJavaDate(value, true);
}
+ return HSSFDateUtil.getJavaDate(value, false);
}
/**
* For blank cells we return an empty string.
* For formulaCells that are not string Formulas, we return empty String
*/
+ public HSSFRichTextString getRichStringCellValue() {
- public HSSFRichTextString getRichStringCellValue()
- {
- if (cellType == CELL_TYPE_BLANK)
- {
- return new HSSFRichTextString("");
- }
- if (cellType == CELL_TYPE_NUMERIC)
- {
- throw new NumberFormatException(
- "You cannot get a string value from a numeric cell");
- }
- 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("");
+ switch(cellType) {
+ case CELL_TYPE_BLANK:
+ return new HSSFRichTextString("");
+ case CELL_TYPE_STRING:
+ return stringValue;
+ default:
+ throw typeMismatch(CELL_TYPE_STRING, cellType, false);
+ case CELL_TYPE_FORMULA:
+ break;
}
- return stringValue;
+ FormulaRecordAggregate fra = ((FormulaRecordAggregate)record);
+ checkFormulaCachedValueType(CELL_TYPE_STRING, fra.getFormulaRecord());
+ String strVal = fra.getStringValue();
+ return new HSSFRichTextString(strVal == null ? "" : strVal);
}
/**
* 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();
short col=record.getColumn();
short styleIndex=record.getXFIndex();
- if ((cellType != CELL_TYPE_BOOLEAN ) && ( cellType != CELL_TYPE_FORMULA))
- {
- setCellType(CELL_TYPE_BOOLEAN, false, row, col, styleIndex);
+
+ switch (cellType) {
+ 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
*
- * @param value the error value to set this cell to. For formulas we'll set the
- * precalculated value ??? IS THIS RIGHT??? , for errors we'll set
+ * @param errorCode the error value to set this cell to. For formulas we'll set the
+ * precalculated value , for errors we'll set
* its value. For other types we will change the cell to an error
* cell and set its value.
*/
-
- public void setCellErrorValue(byte value)
- {
+ public void setCellErrorValue(byte errorCode) {
int row=record.getRow();
short col=record.getColumn();
short styleIndex=record.getXFIndex();
- if (cellType != CELL_TYPE_ERROR) {
- setCellType(CELL_TYPE_ERROR, false, row, col, styleIndex);
+ switch (cellType) {
+ 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/>
- *
- * 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
* 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
*/
private boolean convertCellValueToBoolean() {
-
+
switch (cellType) {
case CELL_TYPE_BOOLEAN:
return (( BoolErrRecord ) record).getBooleanValue();
// All other cases convert to false
// These choices are not well justified.
- case CELL_TYPE_FORMULA:
+ case CELL_TYPE_FORMULA:
// should really evaluate, but HSSFCell can't call HSSFFormulaEvaluator
case CELL_TYPE_ERROR:
case CELL_TYPE_BLANK:
- return false;
+ return false;
}
throw new RuntimeException("Unexpected cell type (" + cellType + ")");
}
* 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.
*/
+ public boolean getBooleanCellValue() {
- public boolean getBooleanCellValue()
- {
- if (cellType == CELL_TYPE_BOOLEAN)
- {
- return (( BoolErrRecord ) record).getBooleanValue();
- }
- if (cellType == CELL_TYPE_BLANK)
- {
- return false;
+ switch(cellType) {
+ case CELL_TYPE_BLANK:
+ return false;
+ case CELL_TYPE_BOOLEAN:
+ return (( BoolErrRecord ) record).getBooleanValue();
+ default:
+ throw typeMismatch(CELL_TYPE_BOOLEAN, cellType, false);
+ case CELL_TYPE_FORMULA:
+ break;
}
- throw new NumberFormatException(
- "You cannot get a boolean value from a non-boolean cell");
+ FormulaRecord fr = ((FormulaRecordAggregate)record).getFormulaRecord();
+ checkFormulaCachedValueType(CELL_TYPE_BOOLEAN, fr);
+ return fr.getCachedBooleanValue();
}
/**
* 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.
*/
-
- public byte getErrorCellValue()
- {
- if (cellType == CELL_TYPE_ERROR)
- {
- return (( BoolErrRecord ) record).getErrorValue();
- }
- if (cellType == CELL_TYPE_BLANK)
- {
- return ( byte ) 0;
+ public byte getErrorCellValue() {
+ switch(cellType) {
+ case CELL_TYPE_ERROR:
+ return (( BoolErrRecord ) record).getErrorValue();
+ default:
+ throw typeMismatch(CELL_TYPE_ERROR, cellType, false);
+ case CELL_TYPE_FORMULA:
+ break;
}
- throw new NumberFormatException(
- "You cannot get an error value from a non-error cell");
+ FormulaRecord fr = ((FormulaRecordAggregate)record).getFormulaRecord();
+ checkFormulaCachedValueType(CELL_TYPE_ERROR, fr);
+ return (byte) fr.getCachedErrorValue();
}
/**
throw new RuntimeException("You cannot reference columns with an index of less then 0.");
}
}
-
+
/**
* Sets this cell as the active cell for the worksheet
*/
this.sheet.setActiveCellRow(row);
this.sheet.setActiveCellCol(col);
}
-
+
/**
* 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
- * knowledge of the semantics of the sheet being processed.
- *
- * Formula cells return the formula string,
- * rather than the formula result.
+ * knowledge of the semantics of the sheet being processed.
+ *
+ * Formula cells return the formula string,
+ * rather than the formula result.
* Dates are displayed in dd-MMM-yyyy format
* Errors are displayed as #ERR<errIdx>
*/
public String toString() {
- switch (getCellType()) {
- case CELL_TYPE_BLANK:
- return "";
- case CELL_TYPE_BOOLEAN:
- return getBooleanCellValue()?"TRUE":"FALSE";
- case CELL_TYPE_ERROR:
- return ErrorEval.getText((( BoolErrRecord ) record).getErrorValue());
- case CELL_TYPE_FORMULA:
- return getCellFormula();
- case CELL_TYPE_NUMERIC:
- //TODO apply the dataformat for this cell
- if (HSSFDateUtil.isCellDateFormatted(this)) {
- DateFormat sdf = new SimpleDateFormat("dd-MMM-yyyy");
- return sdf.format(getDateCellValue());
- } else {
- return getNumericCellValue() + "";
- }
- case CELL_TYPE_STRING:
- return getStringCellValue();
- default:
- return "Unknown Cell Type: " + getCellType();
- }
+ switch (getCellType()) {
+ case CELL_TYPE_BLANK:
+ return "";
+ case CELL_TYPE_BOOLEAN:
+ return getBooleanCellValue()?"TRUE":"FALSE";
+ case CELL_TYPE_ERROR:
+ return ErrorEval.getText((( BoolErrRecord ) record).getErrorValue());
+ case CELL_TYPE_FORMULA:
+ return getCellFormula();
+ case CELL_TYPE_NUMERIC:
+ //TODO apply the dataformat for this cell
+ if (HSSFDateUtil.isCellDateFormatted(this)) {
+ DateFormat sdf = new SimpleDateFormat("dd-MMM-yyyy");
+ return sdf.format(getDateCellValue());
+ } else {
+ return getNumericCellValue() + "";
+ }
+ case CELL_TYPE_STRING:
+ return getStringCellValue();
+ default:
+ return "Unknown Cell Type: " + getCellType();
+ }
}
/**
* @param comment comment associated with this cell
*/
public void setCellComment(HSSFComment comment){
- if(comment == null) {
- removeCellComment();
- return;
- }
-
+ if(comment == null) {
+ removeCellComment();
+ return;
+ }
+
comment.setRow((short)record.getRow());
comment.setColumn(record.getColumn());
this.comment = comment;
}
return comment;
}
-
+
/**
* Removes the comment for this cell, if
* there is one.
* all comments after performing this action!
*/
public void removeCellComment() {
- HSSFComment comment = findCellComment(sheet, record.getRow(), record.getColumn());
- this.comment = null;
-
- if(comment == null) {
- // Nothing to do
- return;
- }
-
- // Zap the underlying NoteRecord
- sheet.getRecords().remove(comment.getNoteRecord());
-
- // If we have a TextObjectRecord, is should
- // be proceeed by:
- // MSODRAWING with container
- // OBJ
- // MSODRAWING with EscherTextboxRecord
- if(comment.getTextObjectRecord() != null) {
- TextObjectRecord txo = comment.getTextObjectRecord();
- int txoAt = sheet.getRecords().indexOf(txo);
-
- if(sheet.getRecords().get(txoAt-3) instanceof DrawingRecord &&
- sheet.getRecords().get(txoAt-2) instanceof ObjRecord &&
- sheet.getRecords().get(txoAt-1) instanceof DrawingRecord) {
- // Zap these, in reverse order
- sheet.getRecords().remove(txoAt-1);
- sheet.getRecords().remove(txoAt-2);
- sheet.getRecords().remove(txoAt-3);
- } else {
- throw new IllegalStateException("Found the wrong records before the TextObjectRecord, can't remove comment");
- }
-
- // Now remove the text record
- sheet.getRecords().remove(txo);
- }
+ HSSFComment comment = findCellComment(sheet, record.getRow(), record.getColumn());
+ this.comment = null;
+
+ if(comment == null) {
+ // Nothing to do
+ return;
+ }
+
+ // Zap the underlying NoteRecord
+ List sheetRecords = sheet.getRecords();
+ sheetRecords.remove(comment.getNoteRecord());
+
+ // If we have a TextObjectRecord, is should
+ // be proceeed by:
+ // MSODRAWING with container
+ // OBJ
+ // MSODRAWING with EscherTextboxRecord
+ if(comment.getTextObjectRecord() != null) {
+ TextObjectRecord txo = comment.getTextObjectRecord();
+ int txoAt = sheetRecords.indexOf(txo);
+
+ if(sheetRecords.get(txoAt-3) instanceof DrawingRecord &&
+ sheetRecords.get(txoAt-2) instanceof ObjRecord &&
+ sheetRecords.get(txoAt-1) instanceof DrawingRecord) {
+ // Zap these, in reverse order
+ sheetRecords.remove(txoAt-1);
+ sheetRecords.remove(txoAt-2);
+ sheetRecords.remove(txoAt-3);
+ } else {
+ throw new IllegalStateException("Found the wrong records before the TextObjectRecord, can't remove comment");
+ }
+
+ // Now remove the text record
+ sheetRecords.remove(txo);
+ }
}
/**
int eofLoc = sheet.findFirstRecordLocBySid( EOFRecord.sid );
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();
+ }
}
// If any references were changed, then
// re-create the formula string
if(changed) {
- c.setCellFormula(
- FormulaParser.toFormulaString(workbook, ptgs)
- );
+ c.setFormulaOnly(ptgs);
}
}
}
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
* with the given internal index, creating one
records.add(new DimensionsRecord());
records.add(new RowRecord(0));
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 RowRecord(2));
records.add(createWindow2Record());
import org.apache.poi.hssf.record.formula.IntPtg;
import org.apache.poi.hssf.record.formula.Ptg;
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
- * class works correctly.
+ * class works correctly.
*
- * @author Andrew C. Oliver
+ * @author Andrew C. Oliver
*/
public final class TestFormulaRecord extends TestCase {
record.setColumn((short)0);
record.setRow(1);
record.setXFIndex((short)4);
-
+
assertEquals(record.getColumn(),0);
assertEquals(record.getRow(), 1);
assertEquals(record.getXFIndex(),4);
}
-
+
/**
* 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() {
- byte[] formulaByte = new byte[29];
-
- 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;
-
+ 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
+
+ };
+
FormulaRecord record = new FormulaRecord(new TestcaseRecordInputStream(FormulaRecord.sid, (short)29, formulaByte));
assertEquals("Row", 0, record.getRow());
- assertEquals("Column", 0, record.getColumn());
- assertTrue("Value is not NaN", Double.isNaN(record.getValue()));
-
+ assertEquals("Column", 0, record.getColumn());
+ assertEquals(HSSFCell.CELL_TYPE_ERROR, record.getCachedResultType());
+
byte[] output = record.serialize();
assertEquals("Output size", 33, output.length); //includes sid+recordlength
-
+
for (int i = 5; i < 13;i++) {
assertEquals("FormulaByte NaN doesn't match", formulaByte[i], output[i+4]);
}
}
-
+
/**
* Tests to see if the shared formula cells properly reserialize the expPtg
*
*/
public void testExpFormula() {
byte[] formulaByte = new byte[27];
-
+
formulaByte[4] =(byte)0x0F;
formulaByte[14]=(byte)0x08;
formulaByte[18]=(byte)0xE0;
assertEquals("Output size", 31, output.length); //includes sid+recordlength
assertEquals("Offset 22", 1, output[26]);
}
-
+
public void testWithConcat() {
// =CHOOSE(2,A2,A3,A4)
byte[] data = {
6, 0, 68, 0,
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
25, 4, 3, 0, // Attr
8, 0, 17, 0, 26, 0, // jumpTable
36, 2, 0, 0, -64, // Ref - A3
25, 8, 12, 0, // Attr
36, 3, 0, 0, -64, // Ref - A4
- 25, 8, 3, 0, // Attr
+ 25, 8, 3, 0, // Attr
66, 4, 100, 0 // CHOOSE
};
RecordInputStream inp = new RecordInputStream( new ByteArrayInputStream(data));
inp.nextRecord();
-
+
FormulaRecord fr = new FormulaRecord(inp);
-
+
Ptg[] ptgs = fr.getParsedExpression();
assertEquals(9, ptgs.length);
assertEquals(IntPtg.class, ptgs[0].getClass());
assertEquals(RefPtg.class, ptgs[6].getClass());
assertEquals(AttrPtg.class, ptgs[7].getClass());
assertEquals(FuncVarPtg.class, ptgs[8].getClass());
-
+
FuncVarPtg choose = (FuncVarPtg)ptgs[8];
assertEquals("CHOOSE", choose.getName());
}
public void testBasic() throws Exception {
FormulaRecord f = new FormulaRecord();
+ f.setCachedResultTypeString();
StringRecord s = new StringRecord();
s.setString("abc");
FormulaRecordAggregate fagg = new FormulaRecordAggregate(f, s, SharedValueManager.EMPTY);
writeOutAndReadBack(wb);
assertTrue("no errors writing sample xls", true);
}
-
+
/**
* Problems with extracting check boxes from
* HSSFObjectData
// Take a look at the embeded objects
List objects = wb.getAllEmbeddedObjects();
assertEquals(1, objects.size());
-
+
HSSFObjectData obj = (HSSFObjectData)objects.get(0);
assertNotNull(obj);
-
+
// Peek inside the underlying record
EmbeddedObjectRefSubRecord rec = obj.findObjectRecord();
assertNotNull(rec);
-
+
assertEquals(32, rec.field_1_stream_id_offset);
assertEquals(0, rec.field_6_stream_id); // WRONG!
assertEquals("Forms.CheckBox.1", rec.field_5_ole_classname);
assertEquals(12, rec.remainingBytes.length);
-
+
// Doesn't have a directory
assertFalse(obj.hasDirectoryEntry());
assertNotNull(obj.getObjectData());
assertEquals(12, obj.getObjectData().length);
assertEquals("Forms.CheckBox.1", obj.getOLE2ClassName());
-
+
try {
obj.getDirectory();
fail();
} catch(FileNotFoundException e) {
- // expectd during successful test
+ // expectd during successful test
} catch (IOException e) {
- throw new RuntimeException(e);
- }
+ throw new RuntimeException(e);
+ }
}
-
+
/**
* Test that we can delete sheets without
* breaking the build in named ranges
HSSFWorkbook wb = openSample("30978-alt.xls");
assertEquals(1, wb.getNumberOfNames());
assertEquals(3, wb.getNumberOfSheets());
-
+
// Check all names fit within range, and use
// DeletedArea3DPtg
Workbook w = wb.getWorkbook();
for(int i=0; i<w.getNumNames(); i++) {
NameRecord r = w.getNameRecord(i);
assertTrue(r.getSheetNumber() <= wb.getNumberOfSheets());
-
+
Ptg[] nd = r.getNameDefinition();
assertEquals(1, nd.length);
assertTrue(nd[0] instanceof DeletedArea3DPtg);
}
-
-
+
+
// Delete the 2nd sheet
wb.removeSheetAt(1);
-
-
+
+
// Re-check
assertEquals(1, wb.getNumberOfNames());
assertEquals(2, wb.getNumberOfSheets());
-
+
for(int i=0; i<w.getNumNames(); i++) {
NameRecord r = w.getNameRecord(i);
assertTrue(r.getSheetNumber() <= wb.getNumberOfSheets());
-
+
Ptg[] nd = r.getNameDefinition();
assertEquals(1, nd.length);
assertTrue(nd[0] instanceof DeletedArea3DPtg);
}
-
-
+
+
// Save and re-load
wb = writeOutAndReadBack(wb);
w = wb.getWorkbook();
-
+
assertEquals(1, wb.getNumberOfNames());
assertEquals(2, wb.getNumberOfSheets());
-
+
for(int i=0; i<w.getNumNames(); i++) {
NameRecord r = w.getNameRecord(i);
assertTrue(r.getSheetNumber() <= wb.getNumberOfSheets());
-
+
Ptg[] nd = r.getNameDefinition();
assertEquals(1, nd.length);
assertTrue(nd[0] instanceof DeletedArea3DPtg);
}
}
-
+
/**
* Test that fonts get added properly
*/
public void test45338() {
HSSFWorkbook wb = new HSSFWorkbook();
assertEquals(4, wb.getNumberOfFonts());
-
+
HSSFSheet s = wb.createSheet();
s.createRow(0);
s.createRow(1);
HSSFCell c1 = s.getRow(0).createCell(0);
HSSFCell c2 = s.getRow(1).createCell(0);
-
+
assertEquals(4, wb.getNumberOfFonts());
-
+
HSSFFont f1 = wb.getFontAt((short)0);
assertEquals(400, f1.getBoldweight());
-
+
// Check that asking for the same font
// multiple times gives you the same thing.
// Otherwise, our tests wouldn't work!
!=
wb.getFontAt((short)2)
);
-
+
// Look for a new font we have
// yet to add
assertNull(
wb.findFont(
- (short)11, (short)123, (short)22,
+ (short)11, (short)123, (short)22,
"Thingy", false, true, (short)2, (byte)2
)
);
-
+
HSSFFont nf = wb.createFont();
assertEquals(5, wb.getNumberOfFonts());
-
+
assertEquals(5, nf.getIndex());
assertEquals(nf, wb.getFontAt((short)5));
-
+
nf.setBoldweight((short)11);
nf.setColor((short)123);
nf.setFontHeight((short)22);
nf.setStrikeout(true);
nf.setTypeOffset((short)2);
nf.setUnderline((byte)2);
-
+
assertEquals(5, wb.getNumberOfFonts());
assertEquals(nf, wb.getFontAt((short)5));
-
+
// Find it now
assertNotNull(
wb.findFont(
- (short)11, (short)123, (short)22,
+ (short)11, (short)123, (short)22,
"Thingy", false, true, (short)2, (byte)2
)
);
assertEquals(
5,
wb.findFont(
- (short)11, (short)123, (short)22,
+ (short)11, (short)123, (short)22,
"Thingy", false, true, (short)2, (byte)2
).getIndex()
);
assertEquals(nf,
wb.findFont(
- (short)11, (short)123, (short)22,
+ (short)11, (short)123, (short)22,
"Thingy", false, true, (short)2, (byte)2
)
);
}
-
+
/**
* From the mailing list - ensure we can handle a formula
* containing a zip code, eg ="70164"
c1.setCellFormula("70164");
c2.setCellFormula("\"70164\"");
c3.setCellFormula("\"90210\"");
-
+
// Check the formulas
assertEquals("70164.0", c1.getCellFormula());
assertEquals("\"70164\"", c2.getCellFormula());
-
+
// And check the values - blank
- assertEquals(0.0, c1.getNumericCellValue(), 0.00001);
- assertEquals("", c1.getRichStringCellValue().getString());
- assertEquals(0.0, c2.getNumericCellValue(), 0.00001);
- assertEquals("", c2.getRichStringCellValue().getString());
- assertEquals(0.0, c3.getNumericCellValue(), 0.00001);
- assertEquals("", c3.getRichStringCellValue().getString());
-
+ confirmCachedValue(0.0, c1);
+ confirmCachedValue(0.0, c2);
+ confirmCachedValue(0.0, c3);
+
// Try changing the cached value on one of the string
// formula cells, so we can see it updates properly
c3.setCellValue(new HSSFRichTextString("test"));
- assertEquals(0.0, c3.getNumericCellValue(), 0.00001);
- assertEquals("test", c3.getRichStringCellValue().getString());
-
-
+ confirmCachedValue("test", c3);
+ 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
HSSFFormulaEvaluator eval = new HSSFFormulaEvaluator(s, wb);
eval.evaluateFormulaCell(c1);
eval.evaluateFormulaCell(c2);
eval.evaluateFormulaCell(c3);
-
+
// Check that the cells now contain
// the correct values
- assertEquals(70164.0, c1.getNumericCellValue(), 0.00001);
- assertEquals("", c1.getRichStringCellValue().getString());
- assertEquals(0.0, c2.getNumericCellValue(), 0.00001);
- assertEquals("70164", c2.getRichStringCellValue().getString());
- assertEquals(0.0, c3.getNumericCellValue(), 0.00001);
- assertEquals("90210", c3.getRichStringCellValue().getString());
-
-
+ confirmCachedValue(70164.0, c1);
+ confirmCachedValue("70164", c2);
+ confirmCachedValue("90210", c3);
+
+
// Write and read
HSSFWorkbook nwb = writeOutAndReadBack(wb);
HSSFSheet ns = nwb.getSheetAt(0);
HSSFCell nc1 = ns.getRow(0).getCell(0);
HSSFCell nc2 = ns.getRow(0).getCell(1);
HSSFCell nc3 = ns.getRow(0).getCell(2);
-
+
// Re-check
- assertEquals(70164.0, nc1.getNumericCellValue(), 0.00001);
- assertEquals("", nc1.getRichStringCellValue().getString());
- assertEquals(0.0, nc2.getNumericCellValue(), 0.00001);
- assertEquals("70164", nc2.getRichStringCellValue().getString());
- assertEquals(0.0, nc3.getNumericCellValue(), 0.00001);
- assertEquals("90210", nc3.getRichStringCellValue().getString());
-
+ confirmCachedValue(70164.0, nc1);
+ confirmCachedValue("70164", nc2);
+ confirmCachedValue("90210", nc3);
+
CellValueRecordInterface[] cvrs = ns.getSheet().getValueRecords();
for (int i = 0; i < cvrs.length; i++) {
CellValueRecordInterface cvr = cvrs[i];
if(cvr instanceof FormulaRecordAggregate) {
FormulaRecordAggregate fr = (FormulaRecordAggregate)cvr;
-
+
if(i == 0) {
assertEquals(70164.0, fr.getFormulaRecord().getValue(), 0.0001);
assertNull(fr.getStringRecord());
}
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
* column which is set to the result of
* {=sin(B1:B9){9,1)[rownum][0]
* In this sample file, the vector column
* is C, and the data column is B.
- *
+ *
* For now, blows up with an exception from ExtPtg
* Expected ExpPtg to be converted from Shared to Non-Shared...
*/
public void DISABLEDtest43623() {
HSSFWorkbook wb = openSample("43623.xls");
assertEquals(1, wb.getNumberOfSheets());
-
+
HSSFSheet s1 = wb.getSheetAt(0);
-
+
HSSFCell c1 = s1.getRow(0).getCell(2);
HSSFCell c2 = s1.getRow(1).getCell(2);
HSSFCell c3 = s1.getRow(2).getCell(2);
-
+
// These formula contents are a guess...
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)[2][0]", c3.getCellFormula());
-
+
// Save and re-open, ensure it still works
HSSFWorkbook nwb = writeOutAndReadBack(wb);
HSSFSheet ns1 = nwb.getSheetAt(0);
HSSFCell nc1 = ns1.getRow(0).getCell(2);
HSSFCell nc2 = ns1.getRow(1).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)[1][0]", nc2.getCellFormula());
assertEquals("{=sin(B1:B9){9,1)[2][0]", nc3.getCellFormula());
}
-
+
/**
* People are all getting confused about the last
* row and cell number
public void test30635() {
HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet s = wb.createSheet();
-
+
// No rows, everything is 0
assertEquals(0, s.getFirstRowNum());
assertEquals(0, s.getLastRowNum());
assertEquals(0, s.getPhysicalNumberOfRows());
-
+
// One row, most things are 0, physical is 1
s.createRow(0);
assertEquals(0, s.getFirstRowNum());
assertEquals(0, s.getLastRowNum());
assertEquals(1, s.getPhysicalNumberOfRows());
-
+
// And another, things change
s.createRow(4);
assertEquals(0, s.getFirstRowNum());
assertEquals(4, s.getLastRowNum());
assertEquals(2, s.getPhysicalNumberOfRows());
-
-
+
+
// Now start on cells
HSSFRow r = s.getRow(0);
assertEquals(-1, r.getFirstCellNum());
assertEquals(-1, r.getLastCellNum());
assertEquals(0, r.getPhysicalNumberOfCells());
-
+
// Add a cell, things move off -1
r.createCell(0);
assertEquals(0, r.getFirstCellNum());
assertEquals(1, r.getLastCellNum()); // last cell # + 1
assertEquals(1, r.getPhysicalNumberOfCells());
-
+
r.createCell(1);
assertEquals(0, r.getFirstCellNum());
assertEquals(2, r.getLastCellNum()); // last cell # + 1
assertEquals(2, r.getPhysicalNumberOfCells());
-
+
r.createCell(4);
assertEquals(0, r.getFirstCellNum());
assertEquals(5, r.getLastCellNum()); // last cell # + 1
assertEquals(3, r.getPhysicalNumberOfCells());
}
-
+
/**
* Data Tables - ptg 0x2
*/
HSSFSheet s;
HSSFRow r;
HSSFCell c;
-
+
// Check the contents of the formulas
-
+
// E4 to G9 of sheet 4 make up the table
s = wb.getSheet("OneVariable Table Completed");
r = s.getRow(3);
c = r.getCell(4);
assertEquals(HSSFCell.CELL_TYPE_FORMULA, c.getCellType());
-
+
// TODO - check the formula once tables and
// arrays are properly supported
-
+
// E4 to H9 of sheet 5 make up the table
s = wb.getSheet("TwoVariable Table Example");
r = s.getRow(3);
c = r.getCell(4);
assertEquals(HSSFCell.CELL_TYPE_FORMULA, c.getCellType());
-
+
// TODO - check the formula once tables and
// arrays are properly supported
}
HSSFSheet sh = wb.getSheetAt(0);
for(short i=0; i < 30; i++) sh.autoSizeColumn(i);
}
-
+
/**
* We used to add too many UncalcRecords to sheets
* with diagrams on. Don't any more
wb.getSheetAt(0).setForceFormulaRecalculation(true);
wb.getSheetAt(1).setForceFormulaRecalculation(false);
wb.getSheetAt(2).setForceFormulaRecalculation(true);
-
+
// Write out and back in again
// This used to break
HSSFWorkbook nwb = writeOutAndReadBack(wb);
-
+
// Check now set as it should be
assertTrue(nwb.getSheetAt(0).getForceFormulaRecalculation());
assertFalse(nwb.getSheetAt(1).getForceFormulaRecalculation());
assertTrue(nwb.getSheetAt(2).getForceFormulaRecalculation());
}
-
+
/**
* Very hidden sheets not displaying as such
*/
public void test45761() {
- HSSFWorkbook wb = openSample("45761.xls");
- assertEquals(3, wb.getNumberOfSheets());
-
- assertFalse(wb.isSheetHidden(0));
- assertFalse(wb.isSheetVeryHidden(0));
- assertTrue(wb.isSheetHidden(1));
- assertFalse(wb.isSheetVeryHidden(1));
- assertFalse(wb.isSheetHidden(2));
- assertTrue(wb.isSheetVeryHidden(2));
-
- // Change 0 to be very hidden, and re-load
- wb.setSheetHidden(0, 2);
-
+ HSSFWorkbook wb = openSample("45761.xls");
+ assertEquals(3, wb.getNumberOfSheets());
+
+ assertFalse(wb.isSheetHidden(0));
+ assertFalse(wb.isSheetVeryHidden(0));
+ assertTrue(wb.isSheetHidden(1));
+ assertFalse(wb.isSheetVeryHidden(1));
+ assertFalse(wb.isSheetHidden(2));
+ assertTrue(wb.isSheetVeryHidden(2));
+
+ // Change 0 to be very hidden, and re-load
+ wb.setSheetHidden(0, 2);
+
HSSFWorkbook nwb = writeOutAndReadBack(wb);
- assertFalse(nwb.isSheetHidden(0));
- assertTrue(nwb.isSheetVeryHidden(0));
- assertTrue(nwb.isSheetHidden(1));
- assertFalse(nwb.isSheetVeryHidden(1));
- assertFalse(nwb.isSheetHidden(2));
- assertTrue(nwb.isSheetVeryHidden(2));
+ assertFalse(nwb.isSheetHidden(0));
+ assertTrue(nwb.isSheetVeryHidden(0));
+ assertTrue(nwb.isSheetHidden(1));
+ assertFalse(nwb.isSheetVeryHidden(1));
+ assertFalse(nwb.isSheetHidden(2));
+ assertTrue(nwb.isSheetVeryHidden(2));
}
}