From 1255f2e0bcf5b8fb102fd5fcb3aeefcb2f03d572 Mon Sep 17 00:00:00 2001 From: Nick Burch Date: Sat, 13 Sep 2008 13:48:27 +0000 Subject: [PATCH] Merged revisions 693591,693639,693658,693939,693941,693947,693990,694050,694065,694153,694534,694615,694619-694620,694631,694643,694877,694881 via svnmerge from https://svn.apache.org/repos/asf/poi/trunk ........ r693591 | josh | 2008-09-09 21:25:16 +0100 (Tue, 09 Sep 2008) | 1 line Added support for parsing array constants in formulas. (Helping investigation for bug 45752) ........ r693639 | josh | 2008-09-09 23:26:28 +0100 (Tue, 09 Sep 2008) | 1 line removed debug code accidentally submitted with r693591 ........ r693658 | josh | 2008-09-10 00:46:46 +0100 (Wed, 10 Sep 2008) | 2 lines Fixed special cases of INDEX function (single columns / single rows, and errors) ........ r693939 | josh | 2008-09-10 20:23:43 +0100 (Wed, 10 Sep 2008) | 1 line Fixing error value handling for numeric functions. Refactored hierarchy. ........ r693941 | josh | 2008-09-10 20:27:24 +0100 (Wed, 10 Sep 2008) | 1 line (Should have been submitted with 693939) Fixing error value handling for numeric functions. Refactored hierarchy. ........ r693947 | josh | 2008-09-10 20:33:58 +0100 (Wed, 10 Sep 2008) | 1 line (Should have been submitted with 693939) Fixing error value handling for numeric functions. Refactored hierarchy. ........ r693990 | josh | 2008-09-10 22:21:28 +0100 (Wed, 10 Sep 2008) | 1 line Refactored hierarchy of MultiOperandNumericFunction. Fixed error value handling. Enabled error value check in TestFormulasFromSpreadsheet ........ r694050 | josh | 2008-09-10 23:43:30 +0100 (Wed, 10 Sep 2008) | 1 line Refactored finance functions. ........ r694065 | josh | 2008-09-11 00:37:22 +0100 (Thu, 11 Sep 2008) | 1 line fixed special cases of MODE function ........ r694153 | josh | 2008-09-11 08:16:20 +0100 (Thu, 11 Sep 2008) | 1 line Refactoring MultiOperandNumericFunction - removed Ref2DEval. ........ r694534 | josh | 2008-09-12 00:18:50 +0100 (Fri, 12 Sep 2008) | 1 line Fix for bug 45639 - cleaned up index logic inside ColumnInfoRecordsAggregate ........ r694615 | josh | 2008-09-12 07:14:07 +0100 (Fri, 12 Sep 2008) | 1 line small tweak to unit test which was silently creating UnknownPtgs ........ r694619 | josh | 2008-09-12 07:58:52 +0100 (Fri, 12 Sep 2008) | 1 line Removed trailing comma from output of HexDump.toHex() ........ r694620 | josh | 2008-09-12 08:03:00 +0100 (Fri, 12 Sep 2008) | 1 line clarification of ArrayPtg size increment ........ r694631 | josh | 2008-09-12 08:43:20 +0100 (Fri, 12 Sep 2008) | 1 line Extended support for cached results of formula cells ........ r694643 | josh | 2008-09-12 09:18:54 +0100 (Fri, 12 Sep 2008) | 2 lines Made HSSFFormulaEvaluator no longer require initialisation with sheet or row. ........ r694877 | josh | 2008-09-13 06:14:26 +0100 (Sat, 13 Sep 2008) | 1 line Refactored TextFunctions. Some minor fixes - test cases added. ........ r694881 | josh | 2008-09-13 06:43:41 +0100 (Sat, 13 Sep 2008) | 1 line Added toString methods formatAsString to CellValue. Changed deprecation on CellValue.getRichTextStringValue ........ git-svn-id: https://svn.apache.org/repos/asf/poi/branches/ooxml@694947 13f79535-47bb-0310-9956-ffa450edef68 --- src/documentation/content/xdocs/changes.xml | 4 + src/documentation/content/xdocs/status.xml | 4 + .../extractor/EventBasedExcelExtractor.java | 199 ++-- .../poi/hssf/extractor/ExcelExtractor.java | 42 +- .../apache/poi/hssf/model/FormulaParser.java | 175 +++- .../hssf/model/OperandClassTransformer.java | 63 +- src/java/org/apache/poi/hssf/model/Sheet.java | 27 +- .../poi/hssf/record/ColumnInfoRecord.java | 140 +-- .../apache/poi/hssf/record/FormulaRecord.java | 686 ++++++++----- .../poi/hssf/record/RecordInputStream.java | 29 +- .../ColumnInfoRecordsAggregate.java | 947 +++++++++--------- .../aggregates/FormulaRecordAggregate.java | 49 +- .../hssf/record/formula/Area2DPtgBase.java | 4 +- .../poi/hssf/record/formula/ArrayPtg.java | 90 +- .../poi/hssf/record/formula/ErrPtg.java | 33 +- .../apache/poi/hssf/record/formula/Ptg.java | 66 +- .../record/formula/atp/AnalysisToolPak.java | 5 +- .../record/formula/atp/ParityFunction.java | 8 +- .../poi/hssf/record/formula/atp/YearFrac.java | 12 +- .../hssf/record/formula/eval/ConcatEval.java | 65 +- .../record/formula/eval/ExternalFunction.java | 6 +- .../record/formula/eval/FunctionEval.java | 159 ++- .../record/formula/eval/OperandResolver.java | 11 +- .../hssf/record/formula/eval/Ref2DEval.java | 53 - .../eval/ValueEvalToNumericXlator.java | 179 ---- .../formula/functions/AggregateFunction.java | 113 +++ .../hssf/record/formula/functions/Atan2.java | 84 -- .../hssf/record/formula/functions/Avedev.java | 76 -- .../record/formula/functions/Average.java | 76 -- .../record/formula/functions/Ceiling.java | 82 -- .../hssf/record/formula/functions/Combin.java | 87 -- .../record/formula/functions/Concatenate.java | 60 -- .../hssf/record/formula/functions/Date.java | 114 --- .../record/formula/functions/DateFunc.java | 76 ++ .../hssf/record/formula/functions/Devsq.java | 77 -- .../hssf/record/formula/functions/Even.java | 2 +- .../hssf/record/formula/functions/Exact.java | 84 -- .../formula/functions/FinanceFunction.java | 130 +-- .../hssf/record/formula/functions/Floor.java | 82 -- .../formula/functions/FreeRefFunction.java | 4 +- .../poi/hssf/record/formula/functions/Fv.java | 75 -- .../hssf/record/formula/functions/Index.java | 58 +- .../record/formula/functions/Indirect.java | 2 +- .../hssf/record/formula/functions/Large.java | 81 -- .../hssf/record/formula/functions/Left.java | 107 -- .../hssf/record/formula/functions/Len.java | 49 - .../hssf/record/formula/functions/Log.java | 87 -- .../hssf/record/formula/functions/Lower.java | 65 -- .../hssf/record/formula/functions/Max.java | 68 -- .../hssf/record/formula/functions/Maxa.java | 66 -- .../hssf/record/formula/functions/Median.java | 77 -- .../hssf/record/formula/functions/Mid.java | 87 -- .../hssf/record/formula/functions/Min.java | 68 -- .../hssf/record/formula/functions/Mina.java | 66 -- .../record/formula/functions/MinaMaxa.java | 40 + .../hssf/record/formula/functions/Mod.java | 87 -- .../hssf/record/formula/functions/Mode.java | 186 ++-- .../MultiOperandNumericFunction.java | 374 ++++--- .../hssf/record/formula/functions/Nper.java | 75 -- .../formula/functions/NumericFunction.java | 390 ++++++-- .../functions/NumericFunctionOneArg.java | 172 ---- .../hssf/record/formula/functions/Odd.java | 2 +- .../hssf/record/formula/functions/Pmt.java | 91 -- .../hssf/record/formula/functions/Power.java | 80 -- .../record/formula/functions/Product.java | 68 -- .../poi/hssf/record/formula/functions/Pv.java | 74 -- .../record/formula/functions/Replace.java | 156 ++- .../hssf/record/formula/functions/Right.java | 108 -- .../hssf/record/formula/functions/Round.java | 83 -- .../record/formula/functions/Rounddown.java | 86 -- .../record/formula/functions/Roundup.java | 88 -- .../hssf/record/formula/functions/Small.java | 81 -- .../record/formula/functions/StatsLib.java | 30 - .../hssf/record/formula/functions/Stdev.java | 76 -- .../record/formula/functions/Substitute.java | 187 ++-- .../hssf/record/formula/functions/Sum.java | 68 -- .../hssf/record/formula/functions/Sumsq.java | 70 -- .../formula/functions/TextFunction.java | 277 +++-- .../hssf/record/formula/functions/Trim.java | 53 - .../hssf/record/formula/functions/Upper.java | 65 -- .../apache/poi/hssf/usermodel/HSSFCell.java | 519 +++++----- .../hssf/usermodel/HSSFFormulaEvaluator.java | 3 + .../apache/poi/hssf/usermodel/HSSFRow.java | 13 +- .../apache/poi/hssf/usermodel/HSSFSheet.java | 44 +- .../poi/hssf/usermodel/HSSFWorkbook.java | 10 + .../poi/hssf/usermodel/LazyAreaEval.java | 29 + .../poi/hssf/usermodel/LazyRefEval.java | 33 + .../ss/usermodel/EvaluationCycleDetector.java | 27 +- .../poi/ss/usermodel/FormulaEvaluator.java | 452 ++++----- .../eval => ss/usermodel}/LazyAreaEval.java | 17 +- .../eval => ss/usermodel}/LazyRefEval.java | 170 ++-- src/java/org/apache/poi/util/HexDump.java | 4 +- src/java/org/apache/poi/util/HexRead.java | 9 +- .../org/apache/poi/ss/usermodel/Cell.java | 2 + .../org/apache/poi/ss/usermodel/Comment.java | 5 +- .../org/apache/poi/ss/usermodel/Cell.java | 2 + .../apache/poi/ddf/TestEscherBSERecord.java | 6 +- .../poi/ddf/TestEscherBlipWMFRecord.java | 22 +- .../poi/ddf/TestEscherChildAnchorRecord.java | 15 +- .../poi/ddf/TestEscherClientAnchorRecord.java | 14 +- .../poi/ddf/TestEscherClientDataRecord.java | 17 +- .../poi/ddf/TestEscherContainerRecord.java | 19 +- .../apache/poi/ddf/TestEscherDgRecord.java | 17 +- .../apache/poi/ddf/TestEscherDggRecord.java | 21 +- .../apache/poi/ddf/TestEscherOptRecord.java | 30 +- .../poi/ddf/TestEscherPropertyFactory.java | 16 +- .../apache/poi/ddf/TestEscherSpRecord.java | 16 +- .../apache/poi/ddf/TestEscherSpgrRecord.java | 18 +- .../ddf/TestEscherSplitMenuColorsRecord.java | 14 +- .../poi/ddf/TestUnknownEscherRecord.java | 15 +- .../poi/hssf/data/FormulaEvalTestData.xls | Bin 154624 -> 157184 bytes .../hssf/data/IndexFunctionTestCaseData.xls | Bin 0 -> 24576 bytes .../org/apache/poi/hssf/data/testRVA.xls | Bin 32256 -> 37888 bytes .../poi/hssf/model/TestFormulaParser.java | 17 + .../poi/hssf/model/TestFormulaParserEval.java | 2 +- .../org/apache/poi/hssf/model/TestRVA.java | 22 +- .../org/apache/poi/hssf/model/TestSheet.java | 9 +- .../poi/hssf/model/TestSheetAdditional.java | 4 +- .../hssf/record/TestDrawingGroupRecord.java | 33 +- .../poi/hssf/record/TestEscherAggregate.java | 2 +- .../poi/hssf/record/TestFormulaRecord.java | 80 +- .../TestColumnInfoRecordsAggregate.java | 100 +- .../TestFormulaRecordAggregate.java | 1 + .../poi/hssf/record/formula/TestArrayPtg.java | 32 +- .../formula/TestExternalFunctionFormulas.java | 2 +- ...TestYearFracCalculatorFromSpreadsheet.java | 2 +- .../formula/eval/TestCircularReferences.java | 10 +- .../formula/eval/TestExternalFunction.java | 2 +- .../record/formula/eval/TestFormulaBugs.java | 6 +- .../eval/TestFormulasFromSpreadsheet.java | 27 +- .../record/formula/eval/TestPercentEval.java | 2 +- .../AllIndividualFunctionEvaluationTests.java | 1 + .../record/formula/functions/TestAverage.java | 53 +- .../formula/functions/TestCountFuncs.java | 4 +- .../record/formula/functions/TestDate.java | 2 +- .../record/formula/functions/TestIndex.java | 4 +- .../TestIndexFunctionFromSpreadsheet.java | 250 +++++ .../record/formula/functions/TestIsBlank.java | 2 +- .../record/formula/functions/TestLen.java | 2 +- .../TestLookupFunctionsFromSpreadsheet.java | 107 +- .../record/formula/functions/TestMid.java | 2 +- .../record/formula/functions/TestPmt.java | 2 +- .../formula/functions/TestRoundFuncs.java | 9 +- .../formula/functions/TestStatsLib.java | 59 +- .../record/formula/functions/TestTrim.java | 2 +- .../poi/hssf/usermodel/TestBug42464.java | 3 +- .../poi/hssf/usermodel/TestBug43093.java | 4 +- .../apache/poi/hssf/usermodel/TestBugs.java | 239 ++--- .../usermodel/TestFormulaEvaluatorBugs.java | 12 +- .../usermodel/TestFormulaEvaluatorDocs.java | 4 +- .../hssf/usermodel/TestHSSFDataFormatter.java | 4 +- .../usermodel/TestHSSFFormulaEvaluator.java | 4 +- 152 files changed, 4427 insertions(+), 6500 deletions(-) delete mode 100644 src/java/org/apache/poi/hssf/record/formula/eval/Ref2DEval.java delete mode 100644 src/java/org/apache/poi/hssf/record/formula/eval/ValueEvalToNumericXlator.java create mode 100644 src/java/org/apache/poi/hssf/record/formula/functions/AggregateFunction.java delete mode 100644 src/java/org/apache/poi/hssf/record/formula/functions/Atan2.java delete mode 100644 src/java/org/apache/poi/hssf/record/formula/functions/Avedev.java delete mode 100644 src/java/org/apache/poi/hssf/record/formula/functions/Average.java delete mode 100644 src/java/org/apache/poi/hssf/record/formula/functions/Ceiling.java delete mode 100644 src/java/org/apache/poi/hssf/record/formula/functions/Combin.java delete mode 100644 src/java/org/apache/poi/hssf/record/formula/functions/Concatenate.java delete mode 100644 src/java/org/apache/poi/hssf/record/formula/functions/Date.java create mode 100644 src/java/org/apache/poi/hssf/record/formula/functions/DateFunc.java delete mode 100644 src/java/org/apache/poi/hssf/record/formula/functions/Devsq.java delete mode 100644 src/java/org/apache/poi/hssf/record/formula/functions/Exact.java delete mode 100644 src/java/org/apache/poi/hssf/record/formula/functions/Floor.java delete mode 100644 src/java/org/apache/poi/hssf/record/formula/functions/Fv.java delete mode 100644 src/java/org/apache/poi/hssf/record/formula/functions/Large.java delete mode 100644 src/java/org/apache/poi/hssf/record/formula/functions/Left.java delete mode 100644 src/java/org/apache/poi/hssf/record/formula/functions/Len.java delete mode 100644 src/java/org/apache/poi/hssf/record/formula/functions/Log.java delete mode 100644 src/java/org/apache/poi/hssf/record/formula/functions/Lower.java delete mode 100644 src/java/org/apache/poi/hssf/record/formula/functions/Max.java delete mode 100644 src/java/org/apache/poi/hssf/record/formula/functions/Maxa.java delete mode 100644 src/java/org/apache/poi/hssf/record/formula/functions/Median.java delete mode 100644 src/java/org/apache/poi/hssf/record/formula/functions/Mid.java delete mode 100644 src/java/org/apache/poi/hssf/record/formula/functions/Min.java delete mode 100644 src/java/org/apache/poi/hssf/record/formula/functions/Mina.java create mode 100644 src/java/org/apache/poi/hssf/record/formula/functions/MinaMaxa.java delete mode 100644 src/java/org/apache/poi/hssf/record/formula/functions/Mod.java delete mode 100644 src/java/org/apache/poi/hssf/record/formula/functions/Nper.java delete mode 100644 src/java/org/apache/poi/hssf/record/formula/functions/NumericFunctionOneArg.java delete mode 100644 src/java/org/apache/poi/hssf/record/formula/functions/Pmt.java delete mode 100644 src/java/org/apache/poi/hssf/record/formula/functions/Power.java delete mode 100644 src/java/org/apache/poi/hssf/record/formula/functions/Product.java delete mode 100644 src/java/org/apache/poi/hssf/record/formula/functions/Pv.java delete mode 100644 src/java/org/apache/poi/hssf/record/formula/functions/Right.java delete mode 100644 src/java/org/apache/poi/hssf/record/formula/functions/Round.java delete mode 100644 src/java/org/apache/poi/hssf/record/formula/functions/Rounddown.java delete mode 100644 src/java/org/apache/poi/hssf/record/formula/functions/Roundup.java delete mode 100644 src/java/org/apache/poi/hssf/record/formula/functions/Small.java delete mode 100644 src/java/org/apache/poi/hssf/record/formula/functions/Stdev.java delete mode 100644 src/java/org/apache/poi/hssf/record/formula/functions/Sum.java delete mode 100644 src/java/org/apache/poi/hssf/record/formula/functions/Sumsq.java delete mode 100644 src/java/org/apache/poi/hssf/record/formula/functions/Trim.java delete mode 100644 src/java/org/apache/poi/hssf/record/formula/functions/Upper.java create mode 100644 src/java/org/apache/poi/hssf/usermodel/LazyAreaEval.java create mode 100644 src/java/org/apache/poi/hssf/usermodel/LazyRefEval.java rename src/java/org/apache/poi/{hssf/record/formula/eval => ss/usermodel}/LazyAreaEval.java (85%) rename src/java/org/apache/poi/{hssf/record/formula/eval => ss/usermodel}/LazyRefEval.java (83%) create mode 100644 src/testcases/org/apache/poi/hssf/data/IndexFunctionTestCaseData.xls create mode 100644 src/testcases/org/apache/poi/hssf/record/formula/functions/TestIndexFunctionFromSpreadsheet.java diff --git a/src/documentation/content/xdocs/changes.xml b/src/documentation/content/xdocs/changes.xml index c1152ef344..6025906757 100644 --- a/src/documentation/content/xdocs/changes.xml +++ b/src/documentation/content/xdocs/changes.xml @@ -65,6 +65,10 @@ Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx + Made HSSFFormulaEvaluator no longer require initialisation with sheet or row + Extended support for cached results of formula cells + 45639 - Fixed AIOOBE due to bad index logic in ColumnInfoRecordsAggregate + Fixed special cases of INDEX function (single column/single row, errors) 45761 - Support for Very Hidden excel sheets in HSSF 45738 - Initial HWPF support for Office Art Shapes 45720 - Fixed HSSFWorkbook.cloneSheet to correctly clone sheets with drawings diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 155d522d7b..9eaab3dad1 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -62,6 +62,10 @@ Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx + Made HSSFFormulaEvaluator no longer require initialisation with sheet or row + Extended support for cached results of formula cells + 45639 - Fixed AIOOBE due to bad index logic in ColumnInfoRecordsAggregate + Fixed special cases of INDEX function (single column/single row, errors) 45761 - Support for Very Hidden excel sheets in HSSF 45738 - Initial HWPF support for Office Art Shapes 45720 - Fixed HSSFWorkbook.cloneSheet to correctly clone sheets with drawings diff --git a/src/java/org/apache/poi/hssf/extractor/EventBasedExcelExtractor.java b/src/java/org/apache/poi/hssf/extractor/EventBasedExcelExtractor.java index 8f3eebb2d3..2ea35c773e 100644 --- a/src/java/org/apache/poi/hssf/extractor/EventBasedExcelExtractor.java +++ b/src/java/org/apache/poi/hssf/extractor/EventBasedExcelExtractor.java @@ -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); + } + } } } } diff --git a/src/java/org/apache/poi/hssf/extractor/ExcelExtractor.java b/src/java/org/apache/poi/hssf/extractor/ExcelExtractor.java index b9750fc58a..17bde6da5f 100644 --- a/src/java/org/apache/poi/hssf/extractor/ExcelExtractor.java +++ b/src/java/org/apache/poi/hssf/extractor/ExcelExtractor.java @@ -14,12 +14,14 @@ 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.ss.usermodel.HeaderFooter; +import org.apache.poi.hssf.record.formula.eval.ErrorEval; import org.apache.poi.hssf.usermodel.HSSFCell; import org.apache.poi.hssf.usermodel.HSSFComment; import org.apache.poi.hssf.usermodel.HSSFRichTextString; @@ -112,40 +114,52 @@ public class ExcelExtractor extends POIOLE2TextExtractor implements org.apache.p int lastCell = row.getLastCellNum(); for(int k=firstCell;k 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 diff --git a/src/java/org/apache/poi/hssf/model/FormulaParser.java b/src/java/org/apache/poi/hssf/model/FormulaParser.java index 5fd30d06e0..32f38d40d1 100644 --- a/src/java/org/apache/poi/hssf/model/FormulaParser.java +++ b/src/java/org/apache/poi/hssf/model/FormulaParser.java @@ -22,11 +22,14 @@ import java.util.List; import java.util.Stack; //import PTGs .. since we need everything, import * +import org.apache.poi.hssf.record.UnicodeString; +import org.apache.poi.hssf.record.constant.ErrorConstant; import org.apache.poi.hssf.record.formula.*; import org.apache.poi.hssf.record.formula.function.FunctionMetadata; import org.apache.poi.hssf.record.formula.function.FunctionMetadataRegistry; import org.apache.poi.ss.usermodel.Name; import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.hssf.usermodel.HSSFErrorConstants; import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.hssf.util.AreaReference; @@ -70,9 +73,9 @@ public final class FormulaParser { public static final int FORMULA_TYPE_ARRAY =2; public static final int FORMULA_TYPE_CONDFORMAT = 3; public static final int FORMULA_TYPE_NAMEDRANGE = 4; - // this constant is currently very specific. The exact differences from general data + // this constant is currently very specific. The exact differences from general data // validation formulas or conditional format formulas is not known yet - public static final int FORMULA_TYPE_DATAVALIDATION_LIST = 5; + public static final int FORMULA_TYPE_DATAVALIDATION_LIST = 5; private final String formulaString; private final int formulaLength; @@ -140,9 +143,9 @@ public final class FormulaParser { /** Report What Was Expected */ private RuntimeException expected(String s) { String msg; - + if (look == '=' && formulaString.substring(0, pointer-1).trim().length() < 1) { - msg = "The specified formula '" + formulaString + msg = "The specified formula '" + formulaString + "' starts with an equals sign which is not allowed."; } else { msg = "Parse error near char " + (pointer-1) + " '" + look + "'" @@ -194,8 +197,8 @@ public final class FormulaParser { /** * Parses a sheet name, named range name, or simple cell reference.
* Note - identifiers in Excel can contain dots, so this method may return a String - * which may need to be converted to an area reference. For example, this method - * may return a value like "A1..B2", in which case the caller must convert it to + * which may need to be converted to an area reference. For example, this method + * may return a value like "A1..B2", in which case the caller must convert it to * an area reference like "A1:B2" */ private String parseIdentifier() { @@ -251,7 +254,7 @@ public final class FormulaParser { } private Ptg parseNameOrReference(String name) { - + AreaReference areaRef = parseArea(name); if (areaRef != null) { // will happen if dots are used instead of colon @@ -373,30 +376,28 @@ public final class FormulaParser { private ParseNode function(String name) { Ptg nameToken = null; if(!AbstractFunctionPtg.isBuiltInFunctionName(name)) { - // user defined function + // user defined function // in the token tree, the name is more or less the first argument - - - int nameIndex = book.getNameIndex(name); - if (nameIndex >= 0) { - Name hName = book.getNameAt(nameIndex); - if (!hName.isFunctionName()) { - throw new FormulaParseException("Attempt to use name '" + name - + "' as a function, but defined name in workbook does not refer to a function"); - } - - // calls to user-defined functions within the workbook - // get a Name token which points to a defined name record - nameToken = new NamePtg(name, this.book); - } else { + int nameIndex = book.getNameIndex(name); + if (nameIndex >= 0) { + Name hName = book.getNameAt(nameIndex); + if (!hName.isFunctionName()) { + throw new FormulaParseException("Attempt to use name '" + name + + "' as a function, but defined name in workbook does not refer to a function"); + } + + // calls to user-defined functions within the workbook + // get a Name token which points to a defined name record + nameToken = new NamePtg(name, this.book); + } else { if(book instanceof HSSFWorkbook) { nameToken = ((HSSFWorkbook)book).getNameXPtg(name); } - if (nameToken == null) { - throw new FormulaParseException("Name '" + name - + "' is completely unknown in the current workbook"); - } - } + if (nameToken == null) { + throw new FormulaParseException("Name '" + name + + "' is completely unknown in the current workbook"); + } + } } Match('('); @@ -544,7 +545,7 @@ public final class FormulaParser { SkipWhite(); switch(look) { case '#': - return new ParseNode(parseErrorLiteral()); + return new ParseNode(ErrPtg.valueOf(parseErrorLiteral())); case '-': Match('-'); return new ParseNode(UnaryMinusPtg.instance, powerFactor()); @@ -557,7 +558,12 @@ public final class FormulaParser { Match(')'); return new ParseNode(ParenthesisPtg.instance, inside); case '"': - return new ParseNode(parseStringLiteral()); + return new ParseNode(new StringPtg(parseStringLiteral())); + case '{': + Match('{'); + ParseNode arrayNode = parseArray(); + Match('}'); + return arrayNode; } if (IsAlpha(look) || look == '\''){ return parseFunctionReferenceOrName(); @@ -567,6 +573,95 @@ public final class FormulaParser { } + private ParseNode parseArray() { + List rowsData = new ArrayList(); + while(true) { + Object[] singleRowData = parseArrayRow(); + rowsData.add(singleRowData); + if (look == '}') { + break; + } + if (look != ';') { + throw expected("'}' or ';'"); + } + Match(';'); + } + int nRows = rowsData.size(); + Object[][] values2d = new Object[nRows][]; + rowsData.toArray(values2d); + int nColumns = values2d[0].length; + checkRowLengths(values2d, nColumns); + + return new ParseNode(new ArrayPtg(values2d)); + } + private void checkRowLengths(Object[][] values2d, int nColumns) { + for (int i = 0; i < values2d.length; i++) { + int rowLen = values2d[i].length; + if (rowLen != nColumns) { + throw new FormulaParseException("Array row " + i + " has length " + rowLen + + " but row 0 has length " + nColumns); + } + } + } + + private Object[] parseArrayRow() { + List temp = new ArrayList(); + while (true) { + temp.add(parseArrayItem()); + SkipWhite(); + switch(look) { + case '}': + case ';': + break; + case ',': + Match(','); + continue; + default: + throw expected("'}' or ','"); + + } + break; + } + + Object[] result = new Object[temp.size()]; + temp.toArray(result); + return result; + } + + private Object parseArrayItem() { + SkipWhite(); + switch(look) { + case '"': return new UnicodeString(parseStringLiteral()); + case '#': return ErrorConstant.valueOf(parseErrorLiteral()); + case 'F': case 'f': + case 'T': case 't': + return parseBooleanLiteral(); + } + // else assume number + return convertArrayNumber(parseNumber()); + } + + private Boolean parseBooleanLiteral() { + String iden = parseIdentifier(); + if ("TRUE".equalsIgnoreCase(iden)) { + return Boolean.TRUE; + } + if ("FALSE".equalsIgnoreCase(iden)) { + return Boolean.FALSE; + } + throw expected("'TRUE' or 'FALSE'"); + } + + private static Double convertArrayNumber(Ptg ptg) { + if (ptg instanceof IntPtg) { + return new Double(((IntPtg)ptg).getValue()); + } + if (ptg instanceof NumberPtg) { + return new Double(((NumberPtg)ptg).getValue()); + } + throw new RuntimeException("Unexpected ptg (" + ptg.getClass().getName() + ")"); + } + private Ptg parseNumber() { String number2 = null; String exponent = null; @@ -603,7 +698,7 @@ public final class FormulaParser { } - private ErrPtg parseErrorLiteral() { + private int parseErrorLiteral() { Match('#'); String part1 = parseIdentifier().toUpperCase(); @@ -611,13 +706,13 @@ public final class FormulaParser { case 'V': if(part1.equals("VALUE")) { Match('!'); - return ErrPtg.VALUE_INVALID; + return HSSFErrorConstants.ERROR_VALUE; } throw expected("#VALUE!"); case 'R': if(part1.equals("REF")) { Match('!'); - return ErrPtg.REF_INVALID; + return HSSFErrorConstants.ERROR_REF; } throw expected("#REF!"); case 'D': @@ -625,21 +720,21 @@ public final class FormulaParser { Match('/'); Match('0'); Match('!'); - return ErrPtg.DIV_ZERO; + return HSSFErrorConstants.ERROR_DIV_0; } throw expected("#DIV/0!"); case 'N': if(part1.equals("NAME")) { Match('?'); // only one that ends in '?' - return ErrPtg.NAME_INVALID; + return HSSFErrorConstants.ERROR_NAME; } if(part1.equals("NUM")) { Match('!'); - return ErrPtg.NUM_ERROR; + return HSSFErrorConstants.ERROR_NUM; } if(part1.equals("NULL")) { Match('!'); - return ErrPtg.NULL_INTERSECTION; + return HSSFErrorConstants.ERROR_NULL; } if(part1.equals("N")) { Match('/'); @@ -648,7 +743,7 @@ public final class FormulaParser { } Match(look); // Note - no '!' or '?' suffix - return ErrPtg.N_A; + return HSSFErrorConstants.ERROR_NA; } throw expected("#NAME?, #NUM!, #NULL! or #N/A"); @@ -701,7 +796,7 @@ public final class FormulaParser { } - private StringPtg parseStringLiteral() { + private String parseStringLiteral() { Match('"'); StringBuffer token = new StringBuffer(); @@ -715,7 +810,7 @@ public final class FormulaParser { token.append(look); GetChar(); } - return new StringPtg(token.toString()); + return token.toString(); } /** Parse and Translate a Math Term */ @@ -972,7 +1067,7 @@ end; } return result; } - + private static String[] getOperands(Stack stack, int nOperands) { String[] operands = new String[nOperands]; diff --git a/src/java/org/apache/poi/hssf/model/OperandClassTransformer.java b/src/java/org/apache/poi/hssf/model/OperandClassTransformer.java index 9b5804f0c4..8b7b56638b 100644 --- a/src/java/org/apache/poi/hssf/model/OperandClassTransformer.java +++ b/src/java/org/apache/poi/hssf/model/OperandClassTransformer.java @@ -74,7 +74,7 @@ final class OperandClassTransformer { + _formulaType + ") not supported yet"); } - transformNode(rootNode, rootNodeOperandClass, false, false); + transformNode(rootNode, rootNodeOperandClass, false); } /** @@ -83,22 +83,35 @@ final class OperandClassTransformer { * the function return value). */ private void transformNode(ParseNode node, byte desiredOperandClass, - boolean callerForceArrayFlag, boolean isDirectChildOfValueOperator) { + boolean callerForceArrayFlag) { Ptg token = node.getToken(); ParseNode[] children = node.getChildren(); + boolean isSimpleValueFunc = isSimpleValueFunction(token); + + if (isSimpleValueFunc) { + boolean localForceArray = desiredOperandClass == Ptg.CLASS_ARRAY; + for (int i = 0; i < children.length; i++) { + transformNode(children[i], desiredOperandClass, localForceArray); + } + setSimpleValueFuncClass((AbstractFunctionPtg) token, desiredOperandClass, callerForceArrayFlag); + return; + } + if (token instanceof ValueOperatorPtg || token instanceof ControlPtg) { // Value Operator Ptgs and Control are base tokens, so token will be unchanged - // but any child nodes are processed according to desiredOperandClass and callerForceArrayFlag + + // As per OOO documentation Sec 3.2.4 "Token Class Transformation", "Step 1" + // All direct operands of value operators that are initially 'R' type will + // be converted to 'V' type. + byte localDesiredOperandClass = desiredOperandClass == Ptg.CLASS_REF ? Ptg.CLASS_VALUE : desiredOperandClass; for (int i = 0; i < children.length; i++) { - ParseNode child = children[i]; - transformNode(child, desiredOperandClass, callerForceArrayFlag, true); + transformNode(children[i], localDesiredOperandClass, callerForceArrayFlag); } return; } if (token instanceof AbstractFunctionPtg) { - transformFunctionNode((AbstractFunctionPtg) token, children, desiredOperandClass, - callerForceArrayFlag); + transformFunctionNode((AbstractFunctionPtg) token, children, desiredOperandClass, callerForceArrayFlag); return; } if (children.length > 0) { @@ -109,15 +122,24 @@ final class OperandClassTransformer { // nothing to do return; } - if (isDirectChildOfValueOperator) { - // As per OOO documentation Sec 3.2.4 "Token Class Transformation", "Step 1" - // All direct operands of value operators that are initially 'R' type will - // be converted to 'V' type. - if (token.getPtgClass() == Ptg.CLASS_REF) { - token.setClass(Ptg.CLASS_VALUE); + token.setClass(transformClass(token.getPtgClass(), desiredOperandClass, callerForceArrayFlag)); + } + + private static boolean isSimpleValueFunction(Ptg token) { + if (token instanceof AbstractFunctionPtg) { + AbstractFunctionPtg aptg = (AbstractFunctionPtg) token; + if (aptg.getDefaultOperandClass() != Ptg.CLASS_VALUE) { + return false; + } + int numberOfOperands = aptg.getNumberOfOperands(); + for (int i=numberOfOperands-1; i>=0; i--) { + if (aptg.getParameterClass(i) != Ptg.CLASS_VALUE) { + return false; + } } + return true; } - token.setClass(transformClass(token.getPtgClass(), desiredOperandClass, callerForceArrayFlag)); + return false; } private byte transformClass(byte currentOperandClass, byte desiredOperandClass, @@ -185,6 +207,7 @@ final class OperandClassTransformer { switch (defaultReturnOperandClass) { case Ptg.CLASS_REF: afp.setClass(Ptg.CLASS_REF); +// afp.setClass(Ptg.CLASS_ARRAY); break; case Ptg.CLASS_VALUE: afp.setClass(Ptg.CLASS_ARRAY); @@ -220,7 +243,17 @@ final class OperandClassTransformer { for (int i = 0; i < children.length; i++) { ParseNode child = children[i]; byte paramOperandClass = afp.getParameterClass(i); - transformNode(child, paramOperandClass, localForceArrayFlag, false); + transformNode(child, paramOperandClass, localForceArrayFlag); + } + } + + private void setSimpleValueFuncClass(AbstractFunctionPtg afp, + byte desiredOperandClass, boolean callerForceArrayFlag) { + + if (callerForceArrayFlag || desiredOperandClass == Ptg.CLASS_ARRAY) { + afp.setClass(Ptg.CLASS_ARRAY); + } else { + afp.setClass(Ptg.CLASS_VALUE); } } } diff --git a/src/java/org/apache/poi/hssf/model/Sheet.java b/src/java/org/apache/poi/hssf/model/Sheet.java index 388f1a6ee4..8bbc30435a 100644 --- a/src/java/org/apache/poi/hssf/model/Sheet.java +++ b/src/java/org/apache/poi/hssf/model/Sheet.java @@ -1055,7 +1055,7 @@ public final class Sheet implements Model { ColumnInfoRecord ci = _columnInfos.findColumnInfo(columnIndex); if (ci != null) { - return ci.getColumnWidth(); + return (short)ci.getColumnWidth(); } //default column width is measured in characters //multiply @@ -1079,8 +1079,8 @@ public final class Sheet implements Model { public short getXFIndexForColAt(short columnIndex) { ColumnInfoRecord ci = _columnInfos.findColumnInfo(columnIndex); if (ci != null) { - return ci.getXFIndex(); - } + return (short)ci.getXFIndex(); + } return 0xF; } @@ -1138,8 +1138,7 @@ public final class Sheet implements Model { * @param indent if true the group will be indented by one level, * if false indenting will be removed by one level. */ - public void groupColumnRange(short fromColumn, short toColumn, boolean indent) - { + public void groupColumnRange(int fromColumn, int toColumn, boolean indent) { // Set the level for each column _columnInfos.groupColumnRange( fromColumn, toColumn, indent); @@ -1709,17 +1708,13 @@ public final class Sheet implements Model { } - public void setColumnGroupCollapsed( short columnNumber, boolean collapsed ) - { - if (collapsed) - { - _columnInfos.collapseColumn( columnNumber ); - } - else - { - _columnInfos.expandColumn( columnNumber ); - } - } + public void setColumnGroupCollapsed(int columnNumber, boolean collapsed) { + if (collapsed) { + _columnInfos.collapseColumn(columnNumber); + } else { + _columnInfos.expandColumn(columnNumber); + } + } /** * protect a spreadsheet with a password (not encypted, just sets protect diff --git a/src/java/org/apache/poi/hssf/record/ColumnInfoRecord.java b/src/java/org/apache/poi/hssf/record/ColumnInfoRecord.java index b77dca3e17..32aef3a6c3 100644 --- a/src/java/org/apache/poi/hssf/record/ColumnInfoRecord.java +++ b/src/java/org/apache/poi/hssf/record/ColumnInfoRecord.java @@ -17,6 +17,7 @@ package org.apache.poi.hssf.record; +import org.apache.poi.util.HexDump; import org.apache.poi.util.LittleEndian; import org.apache.poi.util.BitField; import org.apache.poi.util.BitFieldFactory; @@ -30,19 +31,24 @@ import org.apache.poi.util.BitFieldFactory; */ public final class ColumnInfoRecord extends Record { public static final short sid = 0x7d; - private short field_1_first_col; - private short field_2_last_col; - private short field_3_col_width; - private short field_4_xf_index; - private short field_5_options; + private int field_1_first_col; + private int field_2_last_col; + private int field_3_col_width; + private int field_4_xf_index; + private int field_5_options; private static final BitField hidden = BitFieldFactory.getInstance(0x01); private static final BitField outlevel = BitFieldFactory.getInstance(0x0700); private static final BitField collapsed = BitFieldFactory.getInstance(0x1000); // Excel seems write values 2, 10, and 260, even though spec says "must be zero" private short field_6_reserved; - public ColumnInfoRecord() - { + /** + * Creates a column info record with default width and format + */ + public ColumnInfoRecord() { + setColumnWidth(2275); + field_5_options = 2; + field_4_xf_index = 0x0f; field_6_reserved = 2; // seems to be the most common value } @@ -90,7 +96,7 @@ public final class ColumnInfoRecord extends Record { * @param fc - the first column index (0-based) */ - public void setFirstColumn(short fc) + public void setFirstColumn(int fc) { field_1_first_col = fc; } @@ -100,7 +106,7 @@ public final class ColumnInfoRecord extends Record { * @param lc - the last column index (0-based) */ - public void setLastColumn(short lc) + public void setLastColumn(int lc) { field_2_last_col = lc; } @@ -110,7 +116,7 @@ public final class ColumnInfoRecord extends Record { * @param cw - column width */ - public void setColumnWidth(short cw) + public void setColumnWidth(int cw) { field_3_col_width = cw; } @@ -121,20 +127,11 @@ public final class ColumnInfoRecord extends Record { * @see org.apache.poi.hssf.record.ExtendedFormatRecord */ - public void setXFIndex(short xfi) + public void setXFIndex(int xfi) { field_4_xf_index = xfi; } - /** - * set the options bitfield - use the bitsetters instead - * @param options - the bitfield raw value - */ - - public void setOptions(short options) - { - field_5_options = options; - } // start options bitfield @@ -146,7 +143,7 @@ public final class ColumnInfoRecord extends Record { public void setHidden(boolean ishidden) { - field_5_options = hidden.setShortBoolean(field_5_options, ishidden); + field_5_options = hidden.setBoolean(field_5_options, ishidden); } /** @@ -155,9 +152,9 @@ public final class ColumnInfoRecord extends Record { * @param olevel -outline level for the cells */ - public void setOutlineLevel(short olevel) + public void setOutlineLevel(int olevel) { - field_5_options = outlevel.setShortValue(field_5_options, olevel); + field_5_options = outlevel.setValue(field_5_options, olevel); } /** @@ -168,7 +165,7 @@ public final class ColumnInfoRecord extends Record { public void setCollapsed(boolean iscollapsed) { - field_5_options = collapsed.setShortBoolean(field_5_options, + field_5_options = collapsed.setBoolean(field_5_options, iscollapsed); } @@ -179,7 +176,7 @@ public final class ColumnInfoRecord extends Record { * @return the first column index (0-based) */ - public short getFirstColumn() + public int getFirstColumn() { return field_1_first_col; } @@ -189,7 +186,7 @@ public final class ColumnInfoRecord extends Record { * @return the last column index (0-based) */ - public short getLastColumn() + public int getLastColumn() { return field_2_last_col; } @@ -199,7 +196,7 @@ public final class ColumnInfoRecord extends Record { * @return column width */ - public short getColumnWidth() + public int getColumnWidth() { return field_3_col_width; } @@ -210,21 +207,18 @@ public final class ColumnInfoRecord extends Record { * @see org.apache.poi.hssf.record.ExtendedFormatRecord */ - public short getXFIndex() + public int getXFIndex() { return field_4_xf_index; } - /** - * get the options bitfield - use the bitsetters instead - * @return the bitfield raw value - */ - - public short getOptions() - { + public int getOptions() { return field_5_options; } - + public void setOptions(int field_5_options) { + this.field_5_options = field_5_options; + } + // start options bitfield /** @@ -244,9 +238,9 @@ public final class ColumnInfoRecord extends Record { * @return outline level for the cells */ - public short getOutlineLevel() + public int getOutlineLevel() { - return outlevel.getShortValue(field_5_options); + return outlevel.getValue(field_5_options); } /** @@ -261,6 +255,31 @@ public final class ColumnInfoRecord extends Record { } // end options bitfield + + public boolean containsColumn(int columnIndex) { + return field_1_first_col <= columnIndex && columnIndex <= field_2_last_col; + } + public boolean isAdjacentBefore(ColumnInfoRecord other) { + return field_2_last_col == other.field_1_first_col - 1; + } + + /** + * @return true if the format, options and column width match + */ + public boolean formatMatches(ColumnInfoRecord other) { + if (field_4_xf_index != other.field_4_xf_index) { + return false; + } + if (field_5_options != other.field_5_options) { + return false; + } + if (field_3_col_width != other.field_3_col_width) { + return false; + } + return true; + } + + public short getSid() { return sid; @@ -269,13 +288,13 @@ public final class ColumnInfoRecord extends Record { public int serialize(int offset, byte [] data) { LittleEndian.putShort(data, 0 + offset, sid); - LittleEndian.putShort(data, 2 + offset, ( short ) 12); - LittleEndian.putShort(data, 4 + offset, getFirstColumn()); - LittleEndian.putShort(data, 6 + offset, getLastColumn()); - LittleEndian.putShort(data, 8 + offset, getColumnWidth()); - LittleEndian.putShort(data, 10 + offset, getXFIndex()); - LittleEndian.putShort(data, 12 + offset, getOptions()); - LittleEndian.putShort(data, 14 + offset, field_6_reserved); + LittleEndian.putUShort(data, 2 + offset, 12); + LittleEndian.putUShort(data, 4 + offset, getFirstColumn()); + LittleEndian.putUShort(data, 6 + offset, getLastColumn()); + LittleEndian.putUShort(data, 8 + offset, getColumnWidth()); + LittleEndian.putUShort(data, 10 + offset, getXFIndex()); + LittleEndian.putUShort(data, 12 + offset, field_5_options); + LittleEndian.putUShort(data, 14 + offset, field_6_reserved); return getRecordSize(); } @@ -286,24 +305,19 @@ public final class ColumnInfoRecord extends Record { public String toString() { - StringBuffer buffer = new StringBuffer(); - - buffer.append("[COLINFO]\n"); - buffer.append("colfirst = ").append(getFirstColumn()) - .append("\n"); - buffer.append("collast = ").append(getLastColumn()) - .append("\n"); - buffer.append("colwidth = ").append(getColumnWidth()) - .append("\n"); - buffer.append("xfindex = ").append(getXFIndex()).append("\n"); - buffer.append("options = ").append(getOptions()).append("\n"); - buffer.append(" hidden = ").append(getHidden()).append("\n"); - buffer.append(" olevel = ").append(getOutlineLevel()) - .append("\n"); - buffer.append(" collapsed = ").append(getCollapsed()) - .append("\n"); - buffer.append("[/COLINFO]\n"); - return buffer.toString(); + StringBuffer sb = new StringBuffer(); + + sb.append("[COLINFO]\n"); + sb.append(" colfirst = ").append(getFirstColumn()).append("\n"); + sb.append(" collast = ").append(getLastColumn()).append("\n"); + sb.append(" colwidth = ").append(getColumnWidth()).append("\n"); + sb.append(" xfindex = ").append(getXFIndex()).append("\n"); + sb.append(" options = ").append(HexDump.shortToHex(field_5_options)).append("\n"); + sb.append(" hidden = ").append(getHidden()).append("\n"); + sb.append(" olevel = ").append(getOutlineLevel()).append("\n"); + sb.append(" collapsed= ").append(getCollapsed()).append("\n"); + sb.append("[/COLINFO]\n"); + return sb.toString(); } public Object clone() { diff --git a/src/java/org/apache/poi/hssf/record/FormulaRecord.java b/src/java/org/apache/poi/hssf/record/FormulaRecord.java index 46e8283dc2..b9616e0db7 100644 --- a/src/java/org/apache/poi/hssf/record/FormulaRecord.java +++ b/src/java/org/apache/poi/hssf/record/FormulaRecord.java @@ -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 null - */ - 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 null if the double value encoded by valueLongBits + * 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>= 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 ""; + case BOOLEAN: return getDataValue() == 0 ? "FALSE" : "TRUE"; + case ERROR_CODE: return ErrorEval.getText(getDataValue()); + case EMPTY: return ""; + } + 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 true 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 null + */ + 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; + } } diff --git a/src/java/org/apache/poi/hssf/record/RecordInputStream.java b/src/java/org/apache/poi/hssf/record/RecordInputStream.java index 12c818b183..fe6a4b2ea3 100755 --- a/src/java/org/apache/poi/hssf/record/RecordInputStream.java +++ b/src/java/org/apache/poi/hssf/record/RecordInputStream.java @@ -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(); diff --git a/src/java/org/apache/poi/hssf/record/aggregates/ColumnInfoRecordsAggregate.java b/src/java/org/apache/poi/hssf/record/aggregates/ColumnInfoRecordsAggregate.java index b24d8c5b45..0f405bd111 100644 --- a/src/java/org/apache/poi/hssf/record/aggregates/ColumnInfoRecordsAggregate.java +++ b/src/java/org/apache/poi/hssf/record/aggregates/ColumnInfoRecordsAggregate.java @@ -18,18 +18,35 @@ package org.apache.poi.hssf.record.aggregates; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.List; import org.apache.poi.hssf.model.RecordStream; import org.apache.poi.hssf.record.ColumnInfoRecord; -import org.apache.poi.hssf.record.Record; /** * @author Glen Stampoultzis - * @version $Id$ */ public final class ColumnInfoRecordsAggregate extends RecordAggregate { + /** + * List of {@link ColumnInfoRecord}s assumed to be in order + */ private final List records; + + + private static final class CIRComparator implements Comparator { + public static final Comparator instance = new CIRComparator(); + private CIRComparator() { + // enforce singleton + } + public int compare(Object a, Object b) { + return compareColInfos((ColumnInfoRecord)a, (ColumnInfoRecord)b); + } + public static int compareColInfos(ColumnInfoRecord a, ColumnInfoRecord b) { + return a.getFirstColumn()-b.getFirstColumn(); + } + } /** * Creates an empty aggregate @@ -37,486 +54,470 @@ public final class ColumnInfoRecordsAggregate extends RecordAggregate { public ColumnInfoRecordsAggregate() { records = new ArrayList(); } - public ColumnInfoRecordsAggregate(RecordStream rs) { - this(); - - while(rs.peekNextClass() == ColumnInfoRecord.class) { - records.add(rs.getNext()); - } - if (records.size() < 1) { - throw new RuntimeException("No column info records found"); - } - } - - /** - * Performs a deep clone of the record - */ - public Object clone() - { - ColumnInfoRecordsAggregate rec = new ColumnInfoRecordsAggregate(); - for (int k = 0; k < records.size(); k++) - { - ColumnInfoRecord ci = ( ColumnInfoRecord ) records.get(k); - ci=(ColumnInfoRecord) ci.clone(); - rec.insertColumn( ci ); - } - return rec; - } - - /** - * Inserts a column into the aggregate (at the end of the list). - */ - public void insertColumn( ColumnInfoRecord col ) - { - records.add( col ); - } - - /** - * Inserts a column into the aggregate (at the position specified - * by idx. - */ - public void insertColumn( int idx, ColumnInfoRecord col ) - { - records.add( idx, col ); - } - - public int getNumColumns( ) - { - return records.size(); - } - - public void visitContainedRecords(RecordVisitor rv) { - int nItems = records.size(); - if (nItems < 1) { - return; - } - for(int i=0; i