]> source.dussan.org Git - poi.git/commitdiff
Extended support for cached results of formula cells
authorJosh Micich <josh@apache.org>
Fri, 12 Sep 2008 07:43:20 +0000 (07:43 +0000)
committerJosh Micich <josh@apache.org>
Fri, 12 Sep 2008 07:43:20 +0000 (07:43 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@694631 13f79535-47bb-0310-9956-ffa450edef68

14 files changed:
src/documentation/content/xdocs/changes.xml
src/documentation/content/xdocs/status.xml
src/java/org/apache/poi/hssf/extractor/EventBasedExcelExtractor.java
src/java/org/apache/poi/hssf/extractor/ExcelExtractor.java
src/java/org/apache/poi/hssf/record/FormulaRecord.java
src/java/org/apache/poi/hssf/record/RecordInputStream.java
src/java/org/apache/poi/hssf/record/aggregates/FormulaRecordAggregate.java
src/java/org/apache/poi/hssf/usermodel/HSSFCell.java
src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java
src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java
src/testcases/org/apache/poi/hssf/model/TestSheet.java
src/testcases/org/apache/poi/hssf/record/TestFormulaRecord.java
src/testcases/org/apache/poi/hssf/record/aggregates/TestFormulaRecordAggregate.java
src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java

index 02d75184a9d0eee44a668ca84bb697241dc5bbf6..f789defe0d2db14733c44a446f5eae1c870aedbd 100644 (file)
@@ -37,6 +37,7 @@
 
                <!-- 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>
index dab203e98d36b0898a881efdfcdab93a8e05cafc..579816b5ba0296578b19e1598c432d2c7fcc6163 100644 (file)
@@ -34,6 +34,7 @@
        <!-- 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>
index 8f3eebb2d35b43fb2c999edf6ab4b0ccae8f630c..2ea35c773ed8a969490c8679035fbd15670f262a 100644 (file)
@@ -14,6 +14,7 @@
    See the License for the specific language governing permissions and
    limitations under the License.
 ==================================================================== */
+
 package org.apache.poi.hssf.extractor;
 
 import java.io.IOException;
@@ -49,10 +50,10 @@ import org.apache.poi.poifs.filesystem.POIFSFileSystem;
 /**
  * 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
@@ -63,8 +64,8 @@ public class EventBasedExcelExtractor extends POIOLE2TextExtractor {
        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;
        }
@@ -98,8 +99,8 @@ public class EventBasedExcelExtractor extends POIOLE2TextExtractor {
        public void setFormulasNotResults(boolean formulasNotResults) {
                this.formulasNotResults = formulasNotResults;
        }
-       
-       
+
+
        /**
         * Retreives the text contents of the file
         */
@@ -107,7 +108,7 @@ public class EventBasedExcelExtractor extends POIOLE2TextExtractor {
                String text = null;
                try {
                        TextListener tl = triggerExtraction();
-                       
+
                        text = tl.text.toString();
                        if(! text.endsWith("\n")) {
                                text = text + "\n";
@@ -115,37 +116,37 @@ public class EventBasedExcelExtractor extends POIOLE2TextExtractor {
                } 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;
@@ -160,7 +161,7 @@ public class EventBasedExcelExtractor extends POIOLE2TextExtractor {
                                if(bof.getType() == BOFRecord.TYPE_WORKSHEET) {
                                        sheetNum++;
                                        rowNum = -1;
-                                       
+
                                        if(includeSheetNames) {
                                                if(text.length() > 0) text.append("\n");
                                                text.append(sheetNames.get(sheetNum));
@@ -170,60 +171,60 @@ public class EventBasedExcelExtractor extends POIOLE2TextExtractor {
                        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;
@@ -235,42 +236,42 @@ public class EventBasedExcelExtractor extends POIOLE2TextExtractor {
                                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);
+                               }
+                       }
                }
        }
 }
index c98757be51f2f47b3106adfec40e9d5673035085..d5dc30d00c0c58030e19d0351231eb0b8c373eed 100644 (file)
    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;
@@ -110,40 +110,52 @@ public class ExcelExtractor extends POIOLE2TextExtractor {
                                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
index 46e8283dc20c30ebc6639bc95b0ed141caa4892d..b9616e0db739028ed7ca5c1a356c9d836215ea99 100644 (file)
@@ -18,6 +18,8 @@
 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;
@@ -32,264 +34,430 @@ import org.apache.poi.util.LittleEndian;
  */
 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;
+       }
 }
 
index 12c818b183f5ae074114a97b01738da5f3a74748..fe6a4b2ea3b70d01b02901bb3d43acae2276fa78 100755 (executable)
@@ -209,30 +209,18 @@ public class RecordInputStream extends InputStream {
     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();
@@ -276,9 +264,6 @@ public class RecordInputStream extends InputStream {
   }
     
   public String readCompressedUnicode(int length) {
-    if(length == 0) {
-        return "";
-    }
     if ((length < 0) || ((remaining() < length) && !isContinueNext())) {
             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");
       }
       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();    
index 68d5f453dce300e1abb1a2119fd767fc18846f6a..06bb53a38dcdc24fcf0690029963da11625f81c2 100644 (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.FormulaRecord;
 import org.apache.poi.hssf.record.Record;
+import org.apache.poi.hssf.record.RecordFormatException;
 import org.apache.poi.hssf.record.StringRecord;
 
 /**
@@ -34,9 +35,9 @@ public final class FormulaRecordAggregate extends RecordAggregate implements Cel
     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
      */
@@ -44,6 +45,14 @@ public final class FormulaRecordAggregate extends RecordAggregate implements Cel
         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);
         }
@@ -52,18 +61,18 @@ public final class FormulaRecordAggregate extends RecordAggregate implements Cel
         _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();
     }
@@ -91,7 +100,7 @@ public final class FormulaRecordAggregate extends RecordAggregate implements Cel
     public String toString() {
         return _formulaRecord.toString();
     }
-   
+
     public void visitContainedRecords(RecordVisitor rv) {
          rv.visitRecord(_formulaRecord);
          Record sharedFormulaRecord = _sharedValueManager.getRecordForFirstCell(_formulaRecord);
@@ -102,11 +111,33 @@ public final class FormulaRecordAggregate extends RecordAggregate implements Cel
              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);
+    }
 }
index c0f655bf51a0adc9f66cc79d067086ee91d238e5..98d7f5b48e1bc801faf7c48effef38c97c60f0e5 100644 (file)
@@ -23,6 +23,7 @@ import java.util.Calendar;
 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;
@@ -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
  * 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.
@@ -82,14 +83,15 @@ public final class HSSFCell {
     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;
 
@@ -122,6 +124,9 @@ public final class HSSFCell {
         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
@@ -144,7 +149,7 @@ public final class HSSFCell {
         stringValue  = null;
         this.book    = book;
         this.sheet   = sheet;
-        
+
         short xfindex = sheet.getXFIndexForColAt(col);
         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
      */
     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()) {
 
@@ -205,13 +210,13 @@ public final class HSSFCell {
         }
         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();
     }
 
     /**
@@ -229,7 +234,7 @@ public final class HSSFCell {
     {
         record.setColumn(num);
     }
-    
+
     /**
      * Updates the cell record's idea of what
      *  column it belongs in (0 based)
@@ -237,7 +242,7 @@ public final class HSSFCell {
      */
     protected void updateCellNum(short num)
     {
-       record.setColumn(num);
+        record.setColumn(num);
     }
 
     /**
@@ -417,14 +422,14 @@ public final class HSSFCell {
                 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);
@@ -453,21 +458,20 @@ public final class HSSFCell {
      *        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;
         }
     }
 
@@ -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
      * 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
@@ -539,31 +543,20 @@ public final class HSSFCell {
             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);
         }
@@ -591,95 +584,95 @@ public final class HSSFCell {
         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);
     }
 
     /**
@@ -700,33 +693,22 @@ public final class HSSFCell {
      * 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);
     }
 
     /**
@@ -737,47 +719,56 @@ public final class HSSFCell {
      *        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();
@@ -788,11 +779,11 @@ public final class HSSFCell {
 
             // 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 + ")");
     }
@@ -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.
      * 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();
     }
 
     /**
@@ -888,7 +880,7 @@ public final class HSSFCell {
           throw new RuntimeException("You cannot reference columns with an index of less then 0.");
       }
     }
-    
+
     /**
      * Sets this cell as the active cell for the worksheet
      */
@@ -899,42 +891,42 @@ public final class HSSFCell {
         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&lt;errIdx&gt;
      */
     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();
+        }
     }
 
     /**
@@ -945,11 +937,11 @@ public final class HSSFCell {
      * @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;
@@ -966,7 +958,7 @@ public final class HSSFCell {
         }
         return comment;
     }
-     
+
     /**
      * Removes the comment for this cell, if
      *  there is one.
@@ -974,40 +966,41 @@ public final class HSSFCell {
      *  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);
+        }
     }
 
     /**
@@ -1100,4 +1093,16 @@ public final class HSSFCell {
         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();
+    }
 }
index 3480dbe6a38fe224a7ccda6280bc21188525889c..f097d562a393dd5a6a6f321f411384e5841bbf47 100644 (file)
@@ -1295,9 +1295,7 @@ public final class HSSFSheet {
                     // If any references were changed, then
                     //  re-create the formula string
                     if(changed) {
-                        c.setCellFormula(
-                             FormulaParser.toFormulaString(workbook, ptgs)
-                        );
+                        c.setFormulaOnly(ptgs);
                     }
                 }
             }
index 3dd31d7a045dd87a2ceca29a61fac879fd5cda86..a708bd16c1aae0a49ebbb75b4be7f9f4c453efd5 100644 (file)
@@ -660,6 +660,16 @@ public class HSSFWorkbook extends POIDocument
         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
index 8321774f3265c0f01086004254044ce8cba657c4..3bc79299b5a8967d47f14105b1a5d8bab197fe15 100644 (file)
@@ -214,7 +214,9 @@ public final class TestSheet extends TestCase {
         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());
index 0d96d737cf08cd0f02c5f02a4ec246718177c8ce..4696539f2385057b0569c13cf3b2d3efcb8f5b89 100644 (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.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 {
 
@@ -40,52 +42,66 @@ 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;
@@ -99,13 +115,13 @@ public final class TestFormulaRecord extends TestCase {
                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
@@ -115,14 +131,14 @@ public final class TestFormulaRecord extends TestCase {
                                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());
@@ -134,7 +150,7 @@ public final class TestFormulaRecord extends TestCase {
                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());
        }
index 9b9535602724607da8c8664aef80794a2f5a66a6..978f400fcd0a87f6e69f56b1dd77adad43ff7290 100644 (file)
@@ -30,6 +30,7 @@ public final class TestFormulaRecordAggregate extends TestCase {
     
     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);
index 6e738adfb3f210c3fe30878ba9f4fc13a80bfaad..0f19a68e076f796b3a98b245d2ea928e34b2312c 100644 (file)
@@ -961,7 +961,7 @@ public final class TestBugs extends TestCase {
         writeOutAndReadBack(wb);
         assertTrue("no errors writing sample xls", true);
     }
-    
+
     /**
      * Problems with extracting check boxes from
      *  HSSFObjectData
@@ -973,35 +973,35 @@ public final class TestBugs extends TestCase {
         // 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
@@ -1011,73 +1011,73 @@ public final class TestBugs extends TestCase {
         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!
@@ -1094,22 +1094,22 @@ public final class TestBugs extends TestCase {
                 !=
                 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);
@@ -1118,32 +1118,32 @@ public final class TestBugs extends TestCase {
         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"
@@ -1160,63 +1160,59 @@ public final class TestBugs extends TestCase {
         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());
@@ -1233,7 +1229,18 @@ public final class TestBugs extends TestCase {
         }
         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
@@ -1242,37 +1249,37 @@ public final class TestBugs extends TestCase {
      *  {=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
@@ -1280,48 +1287,48 @@ public final class TestBugs extends TestCase {
     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
      */
@@ -1330,25 +1337,25 @@ public final class TestBugs extends TestCase {
         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
     }
@@ -1361,7 +1368,7 @@ public final class TestBugs extends TestCase {
         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
@@ -1371,41 +1378,41 @@ public final class TestBugs extends TestCase {
         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));
     }
 }