From 1fc10ad6693a5ea100ae5fbbe004bdc2c0723602 Mon Sep 17 00:00:00 2001 From: Ugo Cei Date: Sat, 8 Mar 2008 11:49:00 +0000 Subject: [PATCH] Merged revisions 627779-634630 via svnmerge from https://svn.apache.org/repos/asf/poi/trunk ........ r627779 | nick | 2008-02-14 16:32:49 +0100 (Thu, 14 Feb 2008) | 1 line In the interests of sanity, stop having hssf test data files in scratchpad and main, go to just having them in main ........ r627788 | nick | 2008-02-14 17:01:10 +0100 (Thu, 14 Feb 2008) | 1 line Big formula update from Josh from bug #44364 - support for Match, NA and SumProduct functions, and initial error support in functions ........ r627999 | nick | 2008-02-15 11:30:10 +0100 (Fri, 15 Feb 2008) | 1 line To avoid confusion and repeated changes in svn, update the TestDataValidation test to output its file (that needs opening in excel to check to output) into the system tmp directory ........ r628027 | nick | 2008-02-15 12:45:13 +0100 (Fri, 15 Feb 2008) | 1 line Fix for bug #44403 - Have mid use the third argument properly, and test ........ r628029 | nick | 2008-02-15 12:53:25 +0100 (Fri, 15 Feb 2008) | 1 line Fix for bug #44413 from Josh - Fix for circular references in INDEX, OFFSET, VLOOKUP formulas, where a cell is actually allowed to reference itself ........ r628033 | nick | 2008-02-15 13:04:42 +0100 (Fri, 15 Feb 2008) | 1 line Fix from Josh from bug #44417 - Improved handling of references for the need to quote the sheet name for some formulas, but not when fetching a sheet by name ........ r628035 | nick | 2008-02-15 13:13:25 +0100 (Fri, 15 Feb 2008) | 1 line Fix from Josh from bug #44421 - Update Match function to properly support Area references ........ r628044 | nick | 2008-02-15 13:59:40 +0100 (Fri, 15 Feb 2008) | 1 line Partial fix for bug #44410 - support whole column ranges such as C:C in the formula evaluator (so SUM(D:D) will now work). However, the formula string will still be displayed wrong ........ r628065 | nick | 2008-02-15 14:50:38 +0100 (Fri, 15 Feb 2008) | 1 line Further support for whole-column references, including formula strings and the evaluator. Also has some new tests for it ........ r628714 | nick | 2008-02-18 14:08:16 +0100 (Mon, 18 Feb 2008) | 1 line Update notice for latest guidance on ooxml xsd licence, and update getting involved to link to the newly released binary file format docs ........ r629552 | nick | 2008-02-20 19:14:30 +0100 (Wed, 20 Feb 2008) | 1 line Patch from Josh from bug #44403 - Further support for unusual, but valid, arguments to the Mid function ........ r629738 | nick | 2008-02-21 11:36:08 +0100 (Thu, 21 Feb 2008) | 1 line Fix from Josh from bug #44456 - Update contrib SViewer to not fail if a HSSFRow is null ........ r629742 | nick | 2008-02-21 11:49:25 +0100 (Thu, 21 Feb 2008) | 1 line Use the right way to figure out how many rows on a sheet, so we display the row number for all of them on the left hand side. Also, tidy up some imports ........ r629755 | nick | 2008-02-21 12:34:25 +0100 (Thu, 21 Feb 2008) | 1 line Fix bug 38921, where HSSFPalette.findSimilar() wasn't working properly, and add tests for it ........ r629821 | nick | 2008-02-21 16:08:44 +0100 (Thu, 21 Feb 2008) | 1 line Patch from Josh from bug #44371 - support for OFFSET function, and various tweaks to the formula evaluator to support this ........ r629829 | nick | 2008-02-21 16:35:59 +0100 (Thu, 21 Feb 2008) | 1 line Patch from Josh from bug #44366 - InputStreams passed to POIFSFileSystem are now automatically closed. A warning is generated for people who might've relied on them not being closed before, and a wrapper to restore the old behaviour is supplied ........ r629831 | nick | 2008-02-21 16:40:34 +0100 (Thu, 21 Feb 2008) | 1 line Patch from Josh from bug #44437 - improved unit test for poifs ........ r629832 | nick | 2008-02-21 16:42:06 +0100 (Thu, 21 Feb 2008) | 1 line Patch from Josh from bug #44437 - improved unit test for poifs ........ r629837 | nick | 2008-02-21 16:48:52 +0100 (Thu, 21 Feb 2008) | 1 line Patch from Josh from bug #44449 - Handle SharedFormulas better, for where there are formulas for the same area on two sheets, and when the shared formula flag is set incorrectly ........ r629849 | nick | 2008-02-21 17:22:18 +0100 (Thu, 21 Feb 2008) | 1 line Add a disabled test for a file with whacky StyleRecords that trigger an AIOOB ........ r629865 | nick | 2008-02-21 17:44:46 +0100 (Thu, 21 Feb 2008) | 1 line At the request of legal-discuss, shuffle the ooxml xsd licence details into LICENSE from NOTICE ........ r630160 | nick | 2008-02-22 12:23:50 +0100 (Fri, 22 Feb 2008) | 1 line Patch from Josh from bug #44450 - VLookup and HLookup support, and improvements to Lookup and Offset ........ r630164 | nick | 2008-02-22 12:40:00 +0100 (Fri, 22 Feb 2008) | 1 line Bug #44471 - Crystal Reports generates files with short StyleRecords, which isn't allowed in the spec. Work around this ........ r633114 | nick | 2008-03-03 16:01:18 +0100 (Mon, 03 Mar 2008) | 1 line Patch from Paolo from bug #44481 - getVerticallyCenter shouldn't take a parameter, but leave the old version in as deprecated for now ........ r633118 | nick | 2008-03-03 16:10:46 +0100 (Mon, 03 Mar 2008) | 1 line Fix from Yegor from bug #44491 - don't have the new style handy POIDocument property stuff break old style hpsf+hssf use ........ r633126 | nick | 2008-03-03 16:26:38 +0100 (Mon, 03 Mar 2008) | 1 line Patch from Josh from bug #44495 - Handle named cell ranges in formulas that have lower case parts ........ r633151 | nick | 2008-03-03 17:09:02 +0100 (Mon, 03 Mar 2008) | 1 line Patch from Josh from bug #44510 - Fix how DVALRecord works with dropdowns ........ r633169 | nick | 2008-03-03 17:55:00 +0100 (Mon, 03 Mar 2008) | 1 line Patch from Josh from bug #44508 - Fix formula evaluation with evaluateInCell on boolean formulas ........ r633205 | nick | 2008-03-03 18:47:36 +0100 (Mon, 03 Mar 2008) | 1 line Fix indent, add more documentation, and make the error message more helpful ........ r633505 | nick | 2008-03-04 16:06:29 +0100 (Tue, 04 Mar 2008) | 1 line Problem files from bug #44501 ........ r633547 | nick | 2008-03-04 17:53:32 +0100 (Tue, 04 Mar 2008) | 1 line Big patch from Josh from bug #44504 - lots of formula parser improvements ........ r633548 | nick | 2008-03-04 17:59:02 +0100 (Tue, 04 Mar 2008) | 1 line Changelog update for last patch ........ r634318 | nick | 2008-03-06 16:54:06 +0100 (Thu, 06 Mar 2008) | 1 line Change the behaviour on short last blocks to be a warning not an exception, as some people seem to have "real" valid files that trigger this. Fixed bug #28231 ........ r634371 | nick | 2008-03-06 19:06:48 +0100 (Thu, 06 Mar 2008) | 1 line Embeded files from bug #44524 ........ r634372 | nick | 2008-03-06 19:13:47 +0100 (Thu, 06 Mar 2008) | 1 line Add broken test for bug #43901 ........ r634617 | nick | 2008-03-07 12:18:02 +0100 (Fri, 07 Mar 2008) | 1 line Patch from Josh from bug #43901 - Correctly update the internal last cell number when adding and removing cells (previously sometimes off-by-one) ........ r634619 | nick | 2008-03-07 12:36:14 +0100 (Fri, 07 Mar 2008) | 1 line Improved support for read-only recommended workbooks, fixing bug #44536 ........ r634630 | nick | 2008-03-07 13:06:18 +0100 (Fri, 07 Mar 2008) | 1 line Patch largely from Josh from bug #44539 - Support for area references in formulas of rows >= 32768 ........ git-svn-id: https://svn.apache.org/repos/asf/poi/branches/ooxml@634936 13f79535-47bb-0310-9956-ffa450edef68 --- build.xml | 7 +- legal/LICENSE | 17 + legal/NOTICE | 25 +- .../poi/hssf/contrib/view/SVRowHeader.java | 15 +- .../hssf/contrib/view/SVTableCellEditor.java | 2 - .../contrib/view/SVTableCellRenderer.java | 5 - .../apache/poi/hssf/contrib/view/SViewer.java | 7 +- .../poi/hssf/contrib/view/SViewerPanel.java | 4 +- src/documentation/content/xdocs/changes.xml | 25 + .../content/xdocs/getinvolved/index.xml | 50 +- .../content/xdocs/hslf/index.xml | 4 +- src/documentation/content/xdocs/status.xml | 30 + src/java/org/apache/poi/POIDocument.java | 21 +- .../apache/poi/hssf/model/FormulaParser.java | 1291 +++++++++-------- .../org/apache/poi/hssf/model/LinkTable.java | 302 ++++ .../apache/poi/hssf/model/RecordStream.java | 65 + .../org/apache/poi/hssf/model/Workbook.java | 248 ++-- .../poi/hssf/record/CRNCountRecord.java | 94 ++ .../org/apache/poi/hssf/record/CRNRecord.java | 99 ++ .../apache/poi/hssf/record/DVALRecord.java | 16 +- .../poi/hssf/record/ExternalNameRecord.java | 179 +++ .../poi/hssf/record/FileSharingRecord.java | 21 +- .../apache/poi/hssf/record/NameRecord.java | 2 +- .../apache/poi/hssf/record/RecordFactory.java | 6 +- .../poi/hssf/record/RecordInputStream.java | 2 +- .../poi/hssf/record/SharedFormulaRecord.java | 84 +- .../apache/poi/hssf/record/StyleRecord.java | 17 +- .../apache/poi/hssf/record/SupBookRecord.java | 209 ++- .../aggregates/ValueRecordsAggregate.java | 34 +- .../record/constant/ConstantValueParser.java | 111 ++ .../record/formula/AbstractFunctionPtg.java | 80 +- .../poi/hssf/record/formula/Area3DPtg.java | 117 +- .../poi/hssf/record/formula/AreaAPtg.java | 6 +- .../apache/poi/hssf/record/formula/AreaI.java | 60 + .../poi/hssf/record/formula/AreaPtg.java | 136 +- .../poi/hssf/record/formula/AreaVPtg.java | 4 +- .../poi/hssf/record/formula/ArrayPtg.java | 27 +- .../poi/hssf/record/formula/ErrPtg.java | 79 +- .../poi/hssf/record/formula/FuncPtg.java | 4 + .../poi/hssf/record/formula/IntPtg.java | 95 +- .../poi/hssf/record/formula/NamePtg.java | 38 +- .../poi/hssf/record/formula/NameXPtg.java | 18 +- .../poi/hssf/record/formula/Ref3DPtg.java | 49 +- .../poi/hssf/record/formula/RefAPtg.java | 16 +- .../poi/hssf/record/formula/RefVPtg.java | 12 +- .../poi/hssf/record/formula/ReferencePtg.java | 76 +- .../record/formula/SheetNameFormatter.java | 2 +- .../apache/poi/hssf/usermodel/HSSFCell.java | 32 +- .../hssf/usermodel/HSSFErrorConstants.java | 76 +- .../poi/hssf/usermodel/HSSFPalette.java | 4 +- .../apache/poi/hssf/usermodel/HSSFRow.java | 18 +- .../apache/poi/hssf/usermodel/HSSFSheet.java | 15 +- .../poi/hssf/usermodel/HSSFWorkbook.java | 16 +- .../apache/poi/hssf/util/AreaReference.java | 261 +++- .../apache/poi/hssf/util/CellReference.java | 242 ++- .../poi/poifs/filesystem/POIFSFileSystem.java | 116 +- .../poi/poifs/storage/RawDataBlock.java | 12 +- .../org/apache/poi/util/LittleEndian.java | 10 + .../poi/hssf/record/formula/eval/AddEval.java | 50 +- .../hssf/record/formula/eval/Area2DEval.java | 55 +- .../hssf/record/formula/eval/Area3DEval.java | 58 +- .../hssf/record/formula/eval/AreaEval.java | 8 +- .../hssf/record/formula/eval/BoolEval.java | 19 +- .../hssf/record/formula/eval/ConcatEval.java | 52 +- .../hssf/record/formula/eval/DivideEval.java | 50 +- .../hssf/record/formula/eval/ErrorEval.java | 105 +- .../formula/eval/EvaluationException.java | 134 ++ .../record/formula/eval/ExternalFunction.java | 81 ++ .../record/formula/eval/FunctionEval.java | 46 +- .../record/formula/eval/MultiplyEval.java | 76 +- .../hssf/record/formula/eval/NameEval.java | 48 + .../record/formula/eval/OperandResolver.java | 277 ++++ .../hssf/record/formula/eval/PowerEval.java | 77 +- .../hssf/record/formula/eval/Ref2DEval.java | 40 +- .../hssf/record/formula/eval/Ref3DEval.java | 41 +- .../poi/hssf/record/formula/eval/RefEval.java | 28 +- .../hssf/record/formula/eval/StringEval.java | 21 +- .../record/formula/eval/StringValueEval.java | 10 +- .../record/formula/eval/SubtractEval.java | 52 +- .../record/formula/eval/UnaryMinusEval.java | 45 +- .../record/formula/eval/UnaryPlusEval.java | 127 +- .../eval/ValueEvalToNumericXlator.java | 148 +- .../hssf/record/formula/functions/Avedev.java | 5 +- .../record/formula/functions/Average.java | 5 +- .../formula/functions/FinanceFunction.java | 13 +- .../formula/functions/FreeRefFunction.java | 57 + .../record/formula/functions/Hlookup.java | 140 +- .../poi/hssf/record/formula/functions/If.java | 24 +- .../record/formula/functions/Indirect.java | 34 +- .../record/formula/functions/Isblank.java | 80 +- .../hssf/record/formula/functions/Len.java | 127 +- .../hssf/record/formula/functions/Lookup.java | 113 +- .../record/formula/functions/LookupUtils.java | 530 +++++++ .../hssf/record/formula/functions/Match.java | 211 ++- .../hssf/record/formula/functions/Maxa.java | 8 +- .../hssf/record/formula/functions/Mid.java | 153 +- .../hssf/record/formula/functions/Mina.java | 6 +- .../MultiOperandNumericFunction.java | 227 ++- .../poi/hssf/record/formula/functions/Na.java | 20 +- .../formula/functions/NumericFunction.java | 2 - .../hssf/record/formula/functions/Offset.java | 347 ++++- .../record/formula/functions/Rounddown.java | 3 + .../record/formula/functions/Roundup.java | 3 + .../hssf/record/formula/functions/Rows.java | 2 +- .../hssf/record/formula/functions/Stdev.java | 5 +- .../record/formula/functions/Sumproduct.java | 226 ++- .../hssf/record/formula/functions/Sumsq.java | 8 +- .../record/formula/functions/Sumx2my2.java | 51 +- .../record/formula/functions/Sumx2py2.java | 51 +- .../record/formula/functions/Sumxmy2.java | 51 +- .../poi/hssf/record/formula/functions/T.java | 44 +- .../hssf/record/formula/functions/Trim.java | 66 +- .../record/formula/functions/Vlookup.java | 140 +- .../formula/functions/XYNumericFunction.java | 233 ++- .../usermodel/EvaluationCycleDetector.java | 150 ++ .../EvaluationCycleDetectorManager.java | 46 + .../hssf/usermodel/HSSFFormulaEvaluator.java | 417 +++--- .../org/apache/poi/hdgf/data/ShortChunk1.vsd | Bin 0 -> 87040 bytes .../org/apache/poi/hdgf/data/ShortChunk2.vsd | Bin 0 -> 74752 bytes .../org/apache/poi/hdgf/data/ShortChunk3.vsd | Bin 0 -> 87040 bytes .../apache/poi/hssf/data/42464-ExpPtg-bad.xls | Bin 141824 -> 0 bytes .../apache/poi/hssf/data/42464-ExpPtg-ok.xls | Bin 143872 -> 0 bytes .../poi/hssf/data/FormulaEvalTestData.xls | Bin 130048 -> 0 bytes .../TestMissingRecordAwareHSSFListener.java | 96 +- .../poi/hssf/model/TestFormulaParserSP.java | 41 +- .../formula/eval/AllFormulaEvalTests.java | 38 + .../formula/eval/GenericFormulaTestCase.java | 147 -- .../formula/eval/TestCircularReferences.java | 125 ++ .../record/formula/eval/TestEverything.java | 59 - .../formula/eval/TestExternalFunction.java | 61 + .../eval/TestFormulasFromSpreadsheet.java | 329 +++++ .../formula/eval/TestUnaryPlusEval.java | 61 + .../AllIndividualFunctionEvaluationTests.java | 10 + .../record/formula/functions/EvalFactory.java | 2 +- .../functions/NumericFunctionInvoker.java | 35 +- .../record/formula/functions/TestAverage.java | 103 ++ .../formula/functions/TestCountFuncs.java | 2 +- .../formula/functions/TestEverything.java | 48 - .../record/formula/functions/TestIsBlank.java | 62 + .../record/formula/functions/TestLen.java | 73 + .../TestLookupFunctionsFromSpreadsheet.java | 385 +++++ .../record/formula/functions/TestMatch.java | 215 +++ .../record/formula/functions/TestMid.java | 115 ++ .../record/formula/functions/TestOffset.java | 92 ++ .../formula/functions/TestRoundFuncs.java | 49 + .../formula/functions/TestSumproduct.java | 120 ++ .../record/formula/functions/TestTFunc.java | 118 ++ .../record/formula/functions/TestTrim.java | 78 + .../functions/TestXYNumericFunction.java | 139 ++ .../poi/hssf/usermodel/TestBug42464.java | 51 +- .../poi/hssf/usermodel/TestBug44410.java | 100 ++ .../poi/hssf/usermodel/TestBug44508.java | 42 + src/testcases/org/apache/poi/AllPOITests.java | 44 + .../org/apache/poi/TestPOIDocumentMain.java | 6 +- .../org/apache/poi/ddf/AllPOIDDFTests.java | 47 + .../poi/hpsf/basic/AllPOIHPSFBasicTests.java | 39 + .../org/apache/poi/hssf/HSSFTests.java | 193 +-- .../org/apache/poi/hssf/data/44297.xls | Bin .../hssf/data/AbnormalSharedFormulaFlag.xls | Bin 0 -> 17920 bytes .../poi/hssf/data/FormulaEvalTestData.xls | Bin 0 -> 136704 bytes .../hssf/data/LookupFunctionsTestCaseData.xls | Bin 0 -> 39936 bytes .../org/apache/poi/hssf/data/MissingBits.xls | Bin .../apache/poi/hssf/data/OddStyleRecord.xls | Bin 0 -> 17408 bytes .../poi/hssf/data/ReadOnlyRecommended.xls | Bin 0 -> 13824 bytes .../poi/hssf/data/SingleLetterRanges.xls | Bin 0 -> 13824 bytes .../poi/hssf/data/TestDataValidation.xls | Bin 21504 -> 0 bytes .../poi/hssf/data/externalFunctionExample.xls | Bin 0 -> 16384 bytes .../apache/poi/hssf/data/logoKarmokar4.png | Bin 0 -> 15792 bytes .../poi/hssf/model/TestFormulaParser.java | 326 ++++- .../poi/hssf/record/AllRecordTests.java | 104 ++ .../poi/hssf/record/TestDVALRecord.java | 55 + .../poi/hssf/record/TestSupBookRecord.java | 85 +- .../record/TestcaseRecordInputStream.java | 28 +- .../aggregates/TestValueRecordsAggregate.java | 134 +- .../record/formula/AbstractPtgTestCase.java | 20 +- .../hssf/record/formula/AllFormulaTests.java | 4 +- .../poi/hssf/record/formula/TestAreaPtg.java | 14 +- .../formula/TestExternalFunctionFormulas.java | 56 + .../poi/hssf/usermodel/AllUserModelTests.java | 71 + .../apache/poi/hssf/usermodel/TestBugs.java | 38 + .../hssf/usermodel/TestDataValidation.java | 2 +- .../poi/hssf/usermodel/TestFormulas.java | 16 +- .../poi/hssf/usermodel/TestHSSFPalette.java | 43 + .../poi/hssf/usermodel/TestHSSFPicture.java | 60 +- .../poi/hssf/usermodel/TestHSSFRow.java | 160 +- .../poi/hssf/usermodel/TestNamedRange.java | 22 +- .../hssf/usermodel/TestPOIFSProperties.java | 98 ++ .../poi/hssf/util/AllHSSFUtilTests.java | 39 + .../poi/hssf/util/TestAreaReference.java | 189 ++- .../poi/hssf/util/TestCellReference.java | 78 +- .../org/apache/poi/poifs/AllPOIFSTests.java | 41 + .../poi/poifs/data/excel_with_embeded.xls | Bin 0 -> 75264 bytes .../poi/poifs/data/ppt_with_embeded.xls | Bin 0 -> 79360 bytes .../poi/poifs/data/visio_with_embeded.xls | Bin 0 -> 90624 bytes .../poi/poifs/data/word_with_embeded.doc | Bin 0 -> 75776 bytes .../filesystem/AllPOIFSFileSystemTests.java | 44 + .../poifs/filesystem/TestEmptyDocument.java | 10 +- .../poifs/filesystem/TestPOIFSFileSystem.java | 136 ++ .../poifs/property/AllPOIFSPropertyTests.java | 22 + .../poifs/storage/AllPOIFSStorageTests.java | 47 + .../poi/poifs/storage/TestRawDataBlock.java | 87 +- .../poifs/storage/TestRawDataBlockList.java | 32 +- .../org/apache/poi/util/AllPOIUtilTests.java | 51 + .../org/apache/poi/util/DummyPOILogger.java | 46 + 204 files changed, 11096 insertions(+), 4046 deletions(-) create mode 100755 src/java/org/apache/poi/hssf/model/LinkTable.java create mode 100755 src/java/org/apache/poi/hssf/model/RecordStream.java create mode 100755 src/java/org/apache/poi/hssf/record/CRNCountRecord.java create mode 100755 src/java/org/apache/poi/hssf/record/CRNRecord.java create mode 100755 src/java/org/apache/poi/hssf/record/ExternalNameRecord.java create mode 100755 src/java/org/apache/poi/hssf/record/constant/ConstantValueParser.java create mode 100644 src/java/org/apache/poi/hssf/record/formula/AreaI.java create mode 100755 src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/EvaluationException.java create mode 100755 src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ExternalFunction.java create mode 100755 src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/NameEval.java create mode 100755 src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/OperandResolver.java create mode 100755 src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/FreeRefFunction.java create mode 100644 src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/LookupUtils.java create mode 100755 src/scratchpad/src/org/apache/poi/hssf/usermodel/EvaluationCycleDetector.java create mode 100755 src/scratchpad/src/org/apache/poi/hssf/usermodel/EvaluationCycleDetectorManager.java create mode 100755 src/scratchpad/testcases/org/apache/poi/hdgf/data/ShortChunk1.vsd create mode 100755 src/scratchpad/testcases/org/apache/poi/hdgf/data/ShortChunk2.vsd create mode 100755 src/scratchpad/testcases/org/apache/poi/hdgf/data/ShortChunk3.vsd delete mode 100644 src/scratchpad/testcases/org/apache/poi/hssf/data/42464-ExpPtg-bad.xls delete mode 100644 src/scratchpad/testcases/org/apache/poi/hssf/data/42464-ExpPtg-ok.xls delete mode 100644 src/scratchpad/testcases/org/apache/poi/hssf/data/FormulaEvalTestData.xls create mode 100755 src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/AllFormulaEvalTests.java delete mode 100644 src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/GenericFormulaTestCase.java create mode 100755 src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestCircularReferences.java delete mode 100644 src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestEverything.java create mode 100755 src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestExternalFunction.java create mode 100644 src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestFormulasFromSpreadsheet.java create mode 100755 src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestUnaryPlusEval.java create mode 100755 src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestAverage.java delete mode 100644 src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestEverything.java create mode 100755 src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestIsBlank.java create mode 100755 src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestLen.java create mode 100644 src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestLookupFunctionsFromSpreadsheet.java create mode 100755 src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestMatch.java create mode 100755 src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestMid.java create mode 100755 src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestOffset.java create mode 100755 src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestRoundFuncs.java create mode 100755 src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestSumproduct.java create mode 100755 src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestTFunc.java create mode 100755 src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestTrim.java create mode 100755 src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestXYNumericFunction.java create mode 100644 src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestBug44410.java create mode 100644 src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestBug44508.java create mode 100755 src/testcases/org/apache/poi/AllPOITests.java create mode 100755 src/testcases/org/apache/poi/ddf/AllPOIDDFTests.java create mode 100755 src/testcases/org/apache/poi/hpsf/basic/AllPOIHPSFBasicTests.java rename src/{scratchpad => }/testcases/org/apache/poi/hssf/data/44297.xls (100%) create mode 100755 src/testcases/org/apache/poi/hssf/data/AbnormalSharedFormulaFlag.xls create mode 100644 src/testcases/org/apache/poi/hssf/data/FormulaEvalTestData.xls create mode 100755 src/testcases/org/apache/poi/hssf/data/LookupFunctionsTestCaseData.xls rename src/{scratchpad => }/testcases/org/apache/poi/hssf/data/MissingBits.xls (100%) create mode 100644 src/testcases/org/apache/poi/hssf/data/OddStyleRecord.xls create mode 100644 src/testcases/org/apache/poi/hssf/data/ReadOnlyRecommended.xls create mode 100644 src/testcases/org/apache/poi/hssf/data/SingleLetterRanges.xls delete mode 100644 src/testcases/org/apache/poi/hssf/data/TestDataValidation.xls create mode 100755 src/testcases/org/apache/poi/hssf/data/externalFunctionExample.xls create mode 100755 src/testcases/org/apache/poi/hssf/data/logoKarmokar4.png create mode 100755 src/testcases/org/apache/poi/hssf/record/AllRecordTests.java create mode 100755 src/testcases/org/apache/poi/hssf/record/TestDVALRecord.java create mode 100755 src/testcases/org/apache/poi/hssf/record/formula/TestExternalFunctionFormulas.java create mode 100755 src/testcases/org/apache/poi/hssf/usermodel/AllUserModelTests.java create mode 100644 src/testcases/org/apache/poi/hssf/usermodel/TestPOIFSProperties.java create mode 100755 src/testcases/org/apache/poi/hssf/util/AllHSSFUtilTests.java create mode 100755 src/testcases/org/apache/poi/poifs/AllPOIFSTests.java create mode 100644 src/testcases/org/apache/poi/poifs/data/excel_with_embeded.xls create mode 100644 src/testcases/org/apache/poi/poifs/data/ppt_with_embeded.xls create mode 100644 src/testcases/org/apache/poi/poifs/data/visio_with_embeded.xls create mode 100644 src/testcases/org/apache/poi/poifs/data/word_with_embeded.doc create mode 100755 src/testcases/org/apache/poi/poifs/filesystem/AllPOIFSFileSystemTests.java create mode 100755 src/testcases/org/apache/poi/poifs/filesystem/TestPOIFSFileSystem.java create mode 100755 src/testcases/org/apache/poi/poifs/property/AllPOIFSPropertyTests.java create mode 100755 src/testcases/org/apache/poi/poifs/storage/AllPOIFSStorageTests.java create mode 100755 src/testcases/org/apache/poi/util/AllPOIUtilTests.java create mode 100644 src/testcases/org/apache/poi/util/DummyPOILogger.java diff --git a/build.xml b/build.xml index 8905dc13f..3c40743da 100644 --- a/build.xml +++ b/build.xml @@ -557,8 +557,7 @@ under the License. - - + @@ -663,7 +662,7 @@ under the License. - + @@ -698,7 +697,7 @@ under the License. - + diff --git a/legal/LICENSE b/legal/LICENSE index d64569567..b547443de 100755 --- a/legal/LICENSE +++ b/legal/LICENSE @@ -200,3 +200,20 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + + + + +Office Open XML (OOXML) xsds: +----------------------------- + +These were downloaded as part of the Office Open XML ECMA Specification +from + +These are included within the Apache POI distribution, and are available +under compatible licensing terms. + +Copyright - ECMA International, "made available without restriction" + http://www.ecma-international.org/memento/Ecmabylaws.htm - section 9.4 +Patent License - Microsoft Open Specification Promise (OSP) + http://www.microsoft.com/interop/osp/ diff --git a/legal/NOTICE b/legal/NOTICE index 41c966ff2..d5d7883b3 100644 --- a/legal/NOTICE +++ b/legal/NOTICE @@ -19,17 +19,14 @@ Since this is a data file, and has no compiled version (the original See http://www.gnome.ru/projects/vsdump_en.html -Office Open XML experimental support: - XML Beans - http://xmlbeans.apache.org/ - Apache Licence Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0 - DOM4J - http://www.dom4j.org/ - BSD Licence - http://www.dom4j.org/license.html - Jaxen - http://jaxen.org/ - Apache Style Licence - http://jaxen.org/license.html - OpenXml4J - http://www.openxml4j.org/ - BSD Licence or Apache Licence Version 2.0 - - http://www.openxml4j.org/Licensing/Default.html - Office Open XML ECMA Specification - - http://www.ecma-international.org/publications/standards/Ecma-376.htm - Microsoft Open Specification Promise (OSP) - - http://www.microsoft.com/interop/osp/ +The Office Open XML experimental support had additional dependencies, +with their own licensing: + * XML Beans - http://xmlbeans.apache.org/ + Apache Licence Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0 + * DOM4J - http://www.dom4j.org/ + BSD Licence - http://www.dom4j.org/license.html + * Jaxen - http://jaxen.org/ + Apache Style Licence - http://jaxen.org/license.html + * OpenXml4J - http://www.openxml4j.org/ + BSD Licence or Apache Licence Version 2.0 - + http://www.openxml4j.org/Licensing/Default.html diff --git a/src/contrib/src/org/apache/poi/hssf/contrib/view/SVRowHeader.java b/src/contrib/src/org/apache/poi/hssf/contrib/view/SVRowHeader.java index f53d9cd89..fe63dfcc8 100644 --- a/src/contrib/src/org/apache/poi/hssf/contrib/view/SVRowHeader.java +++ b/src/contrib/src/org/apache/poi/hssf/contrib/view/SVRowHeader.java @@ -21,11 +21,8 @@ package org.apache.poi.hssf.contrib.view; import java.awt.*; -import java.awt.event.*; -import java.io.*; import javax.swing.*; import javax.swing.table.*; -import javax.swing.event.*; import org.apache.poi.hssf.usermodel.*; @@ -47,7 +44,9 @@ public class SVRowHeader extends JList { this.sheet = sheet; } - public int getSize() { return sheet.getPhysicalNumberOfRows(); } + public int getSize() { + return sheet.getLastRowNum() + 1; + } public Object getElementAt(int index) { return Integer.toString(index+1); } @@ -73,7 +72,13 @@ public class SVRowHeader extends JList { public Component getListCellRendererComponent( JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { Dimension d = getPreferredSize(); - int rowHeight = (int)sheet.getRow(index).getHeightInPoints(); + HSSFRow row = sheet.getRow(index); + int rowHeight; + if(row == null) { + rowHeight = (int)sheet.getDefaultRowHeightInPoints(); + } else { + rowHeight = (int)row.getHeightInPoints(); + } d.height = rowHeight+extraHeight; setPreferredSize(d); setText((value == null) ? "" : value.toString()); diff --git a/src/contrib/src/org/apache/poi/hssf/contrib/view/SVTableCellEditor.java b/src/contrib/src/org/apache/poi/hssf/contrib/view/SVTableCellEditor.java index a433a7a11..8b421ceaf 100644 --- a/src/contrib/src/org/apache/poi/hssf/contrib/view/SVTableCellEditor.java +++ b/src/contrib/src/org/apache/poi/hssf/contrib/view/SVTableCellEditor.java @@ -20,11 +20,9 @@ package org.apache.poi.hssf.contrib.view; import java.awt.*; import java.awt.event.*; -import java.text.*; import java.util.*; import javax.swing.*; -import javax.swing.border.*; import javax.swing.table.*; import org.apache.poi.hssf.usermodel.*; diff --git a/src/contrib/src/org/apache/poi/hssf/contrib/view/SVTableCellRenderer.java b/src/contrib/src/org/apache/poi/hssf/contrib/view/SVTableCellRenderer.java index 99333874e..0e4873b5d 100644 --- a/src/contrib/src/org/apache/poi/hssf/contrib/view/SVTableCellRenderer.java +++ b/src/contrib/src/org/apache/poi/hssf/contrib/view/SVTableCellRenderer.java @@ -19,8 +19,6 @@ package org.apache.poi.hssf.contrib.view; -import java.util.Hashtable; - import javax.swing.*; import javax.swing.table.TableCellRenderer; import javax.swing.border.*; @@ -28,14 +26,11 @@ import javax.swing.border.*; import java.awt.Component; import java.awt.Color; import java.awt.Rectangle; -import java.awt.Font; import java.io.Serializable; import java.text.*; import org.apache.poi.hssf.usermodel.*; -import org.apache.poi.hssf.util.HSSFColor; - /** diff --git a/src/contrib/src/org/apache/poi/hssf/contrib/view/SViewer.java b/src/contrib/src/org/apache/poi/hssf/contrib/view/SViewer.java index 7d451d7c7..a3668f649 100644 --- a/src/contrib/src/org/apache/poi/hssf/contrib/view/SViewer.java +++ b/src/contrib/src/org/apache/poi/hssf/contrib/view/SViewer.java @@ -23,13 +23,10 @@ package org.apache.poi.hssf.contrib.view; import java.awt.*; import java.awt.event.*; import java.net.*; -import java.applet.*; import java.io.*; import javax.swing.*; import org.apache.poi.hssf.usermodel.HSSFWorkbook; -import org.apache.poi.hssf.usermodel.HSSFSheet; -import org.apache.poi.hssf.usermodel.HSSFCell; /** * Sheet Viewer - Views XLS files via HSSF. Can be used as an applet with @@ -143,6 +140,10 @@ public class SViewer extends JApplet { /**Main method*/ public static void main(String[] args) { + if(args.length < 1) { + throw new IllegalArgumentException("A filename to view must be supplied as the first argument, but none was given"); + } + SViewer applet = new SViewer(); applet.isStandalone = true; applet.filename = args[0]; diff --git a/src/contrib/src/org/apache/poi/hssf/contrib/view/SViewerPanel.java b/src/contrib/src/org/apache/poi/hssf/contrib/view/SViewerPanel.java index 3cd2c1c06..c134ffd54 100644 --- a/src/contrib/src/org/apache/poi/hssf/contrib/view/SViewerPanel.java +++ b/src/contrib/src/org/apache/poi/hssf/contrib/view/SViewerPanel.java @@ -25,7 +25,6 @@ import java.awt.event.*; import java.io.*; import javax.swing.*; import javax.swing.table.*; -import javax.swing.event.*; import org.apache.poi.hssf.usermodel.*; @@ -260,6 +259,9 @@ public class SViewerPanel extends JPanel { /**Main method*/ public static void main(String[] args) { + if(args.length < 1) { + throw new IllegalArgumentException("A filename to view must be supplied as the first argument, but none was given"); + } try { FileInputStream in = new FileInputStream(args[0]); HSSFWorkbook wb = new HSSFWorkbook(in); diff --git a/src/documentation/content/xdocs/changes.xml b/src/documentation/content/xdocs/changes.xml index 34345df5d..c355afd44 100644 --- a/src/documentation/content/xdocs/changes.xml +++ b/src/documentation/content/xdocs/changes.xml @@ -36,6 +36,31 @@ + 44539 - Support for area references in formulas of rows >= 32768 + 44536 - Improved support for detecting read-only recommended files + 43901 - Correctly update the internal last cell number when adding and removing cells (previously sometimes off-by-one) + 28231 - For apparently truncated files, which are somehow still valid, now issue a truncation warning but carry on, rather than giving an exception as before + 44504 - Added initial support for recognising external functions like YEARFRAC and ISEVEN (using NameXPtg), via LinkTable support + 44504 - Improvements to FormulaParser - operators, precedence, error literals, quotes in string literals, range checking on IntPtg, formulas with extra un-parsed stuff at the end, improved parse error handling + 44504 - Fixed number conversion inconsistencies in many functions, and improved RefEval + 44508 - Fix formula evaluation with evaluateInCell on boolean formulas + 44510 - Fix how DVALRecord works with dropdowns + 44495 - Handle named cell ranges in formulas that have lower case parts + 44491 - Don't have the new-style "HPSF properties are always available" affect the old-style use of HPSF alongside HSSF + 44471 - Crystal Reports generates files with short StyleRecords, which isn't allowed in the spec. Work around this + 44450 - Support for Lookup, HLookup and VLookup functions + 44449 - Avoid getting confused when two sheets have shared formulas for the same areas, and when the shared formula is set incorrectly + 44366 - InputStreams passed to POIFSFileSystem are now automatically closed. A warning is generated for people who might've relied on them not being closed before, and a wrapper to restore the old behaviour is supplied + 44371 - Support for the Offset function + 38921 - Have HSSFPalette.findSimilar() work properly + 44456 - Fix the contrib SViewer / SViewerPanel to not fail on sheets with missing rows + 44403 - Further support for unusual, but valid, arguments to the Mid function + 44410 - Support for whole-column ranges, such as C:C, in formula strings and the formula evaluator + 44421 - Update Match function to properly support Area references + 44417 - Improved handling of references for the need to quote the sheet name for some formulas, but not when fetching a sheet by name + 44413 - Fix for circular references in INDEX, OFFSET, VLOOKUP formulas, where a cell is actually allowed to reference itself + 44403 - Fix for Mid function handling its arguments wrong + 44364 - Support for Match, NA and SumProduct functions, as well as initial function error support 44375 - Cope with a broken dictionary in Document Summary Information stream. RuntimeExceptions that occured when trying to read bogus data are now caught. Dictionary entries up to but not including the bogus one are preserved, the rest is ignored. 38641 - Handle timezones better with cell.setCellValue(Calendar), so now 20:00-03:00, 20:00+00:00 and 20:00+03:00 will all be recorded as 20:00, and not 17:00 / 20:00 / 23:00 (pass a Date not a Calendar for old behaviour) 44373 - Have HSSFDateUtil.isADateFormat recognize more formats as being dates diff --git a/src/documentation/content/xdocs/getinvolved/index.xml b/src/documentation/content/xdocs/getinvolved/index.xml index 71ba94134..eeab7c6e7 100644 --- a/src/documentation/content/xdocs/getinvolved/index.xml +++ b/src/documentation/content/xdocs/getinvolved/index.xml @@ -53,6 +53,43 @@ license.

+
Publicly Available Information on the file formats +

+ In early 2008, Microsoft made a fairly complete set of documentation + on the binary file formats freely and publicly available. These were + released under the Open + Specification Promise, which does allow us to use them for + building open source software under the + Apache Software License. +

+

+ You can download the documentation on Excel, Word, PowerPoint and + Escher (drawing) from + http://www.microsoft.com/interop/docs/OfficeBinaryFormats.mspx. + Documentation on a few of the supporting technologies used in these + file formats can be downloaded from + http://www.microsoft.com/interop/docs/supportingtechnologies.mspx. +

+

+ Previously, Microsoft published a book on the Excel 97 file format. + It can still be of plenty of use, and is handy dead tree form. Pick up + a copy of "Excel 97 Developer's Kit" from your favourite second hand + book store. +

+

+ The newer Office Open XML (ooxml) file formats are documented as part + of the ECMA / ISO standardisation effort for the formats. This + documentation is quite large, but you can normally find the bit you + need without too much effort! This can be downloaded from + http://www.ecma-international.org/publications/standards/Ecma-376.htm, + and is also under the OSP. +

+

+ It is also worth checking the documentation and code of the other + open source implementations of the file formats. +

+
I just signed an NDA to get a spec from Microsoft and I'd like to contribute

In short, stay away, stay far far away. Implementing these file formats @@ -66,13 +103,14 @@

If you've ever received information regarding the OLE 2 Compound Document Format under any type of exclusionary agreement from Microsoft, or - (probably illegally) received such information from a person bound by - such an agreement, you cannot participate in this project. (Sorry) + (possibly illegally) received such information from a person bound by + such an agreement, you cannot participate in this project. (Sorry)

Those submitting patches that show insight into the file format may be - asked to state explicitly that they are eligible or possibly sign an - agreement. + asked to state explicitly that they have only ever read the publicly + available file format information, and not any received under an NDA + or similar.

@@ -86,7 +124,9 @@
  • Documentation is always the best place to start contributing, maybe you found that if the documentation just told you how to do X then it would make more sense, modify the documentation.
  • Get used to building POI, you'll be doing it a lot, be one with the build, know its targets, etc.
  • Write Unit Tests. Great way to understand POI. Look for classes that aren't tested, or aren't tested on a public/protected method level, start there.
  • -
  • (HSSF)Get the Excel 97 Developer's Kit - its out of print but its dirt cheap (seen copies for under $15 (US)) used on amazon. It explains the Excel file format.
  • +
  • Download the file format documentation from Microsoft - + OLE2 Binary File Formats or + OOXML XML File Formats
  • Submit patches (see below) of your contributions, modifications.
  • Fill out new features, see Bug database for suggestions.
  • diff --git a/src/documentation/content/xdocs/hslf/index.xml b/src/documentation/content/xdocs/hslf/index.xml index a6eaf6711..438aab3f1 100755 --- a/src/documentation/content/xdocs/hslf/index.xml +++ b/src/documentation/content/xdocs/hslf/index.xml @@ -45,8 +45,8 @@ scratchpad area of the POI SVN repository. Ensure that you have the scratchpad jar or the scratchpad - build area in your - classpath before experimenting with this code. + build area in your classpath before experimenting with + this code - the main POI jar is not enough.

    The quick guide documentation provides information on using this API. Comments and fixes gratefully accepted on the POI diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 91641b13b..c59cae1a3 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -33,6 +33,36 @@ + 44539 - Support for area references in formulas of rows >= 32768 + 44536 - Improved support for detecting read-only recommended files + 43901 - Correctly update the internal last cell number when adding and removing cells (previously sometimes off-by-one) + 44504 - Added initial support for recognising external functions like YEARFRAC and ISEVEN (using NameXPtg), via LinkTable support + 44504 - Improvements to FormulaParser - operators, precedence, error literals, quotes in string literals, range checking on IntPtg, formulas with extra un-parsed stuff at the end, improved parse error handling + 44504 - Fixed number conversion inconsistencies in many functions, and improved RefEval + 44504 - Added initial support for recognising external functions like YEARFRAC and ISEVEN (using NameXPtg), via LinkTable support + 44504 - Improvements to FormulaParser - operators, precedence, error literals, quotes in string literals, range checking on IntPtg, formulas with extra un-parsed stuff at the end, improved parse error handling + 44504 - Fixed number conversion inconsistencies in many functions, and improved RefEval + 44508 - Fix formula evaluation with evaluateInCell on boolean formulas + 44510 - Fix how DVALRecord works with dropdowns + 44495 - Handle named cell ranges in formulas that have lower case parts + 44491 - Don't have the new-style "HPSF properties are always available" affect the old-style use of HPSF alongside HSSF + 44471 - Crystal Reports generates files with short StyleRecords, which isn't allowed in the spec. Work around this + 44495 - Handle named cell ranges in formulas that have lower case parts + 44491 - Don't have the new-style "HPSF properties are always available" affect the old-style use of HPSF alongside HSSF + 44471 - Crystal Reports generates files with short StyleRecords, which isn't allowed in the spec. Work around this + 44450 - Support for Lookup, HLookup and VLookup functions + 44449 - Avoid getting confused when two sheets have shared formulas for the same areas, and when the shared formula is set incorrectly + 44366 - InputStreams passed to POIFSFileSystem are now automatically closed. A warning is generated for people who might've relied on them not being closed before, and a wrapper to restore the old behaviour is supplied + 44371 - Support for the Offset function + 38921 - Have HSSFPalette.findSimilar() work properly + 44456 - Fix the contrib SViewer / SViewerPanel to not fail on sheets with missing rows + 44403 - Further support for unusual, but valid, arguments to the Mid function + 44410 - Support for whole-column ranges, such as C:C, in formula strings and the formula evaluator + 44421 - Update Match function to properly support Area references + 44417 - Improved handling of references for the need to quote the sheet name for some formulas, but not when fetching a sheet by name + 44413 - Fix for circular references in INDEX, OFFSET, VLOOKUP formulas, where a cell is actually allowed to reference itself + 44403 - Fix for Mid function handling its arguments wrong + 44364 - Support for Match, NA and SumProduct functions, as well as initial function error support 44375 - Cope with a broken dictionary in Document Summary Information stream. RuntimeExceptions that occured when trying to read bogus data are now caught. Dictionary entries up to but not including the bogus one are preserved, the rest is ignored. 38641 - Handle timezones better with cell.setCellValue(Calendar), so now 20:00-03:00, 20:00+00:00 and 20:00+03:00 will all be recorded as 20:00, and not 17:00 / 20:00 / 23:00 (pass a Date not a Calendar for old behaviour) 44373 - Have HSSFDateUtil.isADateFormat recognize more formats as being dates diff --git a/src/java/org/apache/poi/POIDocument.java b/src/java/org/apache/poi/POIDocument.java index 8d91c06e7..075fa4538 100644 --- a/src/java/org/apache/poi/POIDocument.java +++ b/src/java/org/apache/poi/POIDocument.java @@ -54,16 +54,24 @@ public abstract class POIDocument { /** For our own logging use */ protected POILogger logger = POILogFactory.getLogger(this.getClass()); - - /** + /* Have the property streams been read yet? (Only done on-demand) */ + protected boolean initialized = false; + + /** * Fetch the Document Summary Information of the document */ - public DocumentSummaryInformation getDocumentSummaryInformation() { return dsInf; } + public DocumentSummaryInformation getDocumentSummaryInformation() { + if(!initialized) readProperties(); + return dsInf; + } /** * Fetch the Summary Information of the document */ - public SummaryInformation getSummaryInformation() { return sInf; } + public SummaryInformation getSummaryInformation() { + if(!initialized) readProperties(); + return sInf; + } /** * Find, and create objects for, the standard @@ -89,6 +97,9 @@ public abstract class POIDocument { } else if(ps != null) { logger.log(POILogger.WARN, "SummaryInformation property set came back with wrong class - ", ps.getClass()); } + + // Mark the fact that we've now loaded up the properties + initialized = true; } /** @@ -133,7 +144,7 @@ public abstract class POIDocument { * @param writtenEntries a list of POIFS entries to add the property names too */ protected void writeProperties(POIFSFileSystem outFS, List writtenEntries) throws IOException { - if(sInf != null) { + if(sInf != null) { writePropertySet(SummaryInformation.DEFAULT_STREAM_NAME,sInf,outFS); if(writtenEntries != null) { writtenEntries.add(SummaryInformation.DEFAULT_STREAM_NAME); diff --git a/src/java/org/apache/poi/hssf/model/FormulaParser.java b/src/java/org/apache/poi/hssf/model/FormulaParser.java index 832dbdef7..7b89b90d8 100644 --- a/src/java/org/apache/poi/hssf/model/FormulaParser.java +++ b/src/java/org/apache/poi/hssf/model/FormulaParser.java @@ -1,4 +1,3 @@ - /* ==================================================================== Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with @@ -15,15 +14,13 @@ See the License for the specific language governing permissions and limitations under the License. ==================================================================== */ - - package org.apache.poi.hssf.model; import java.util.ArrayList; import java.util.Iterator; -import java.util.LinkedList; import java.util.List; +import java.util.Stack; import java.util.regex.Pattern; //import PTG's .. since we need everything, import * @@ -33,7 +30,7 @@ import org.apache.poi.hssf.record.formula.*; /** * This class parses a formula string into a List of tokens in RPN order. - * Inspired by + * Inspired by * Lets Build a Compiler, by Jack Crenshaw * BNF for the formula expression is : * ::= [ ]* @@ -48,138 +45,124 @@ import org.apache.poi.hssf.record.formula.*; * @author Peter M. Murray (pete at quantrix dot com) * @author Pavel Krupets (pkrupets at palmtreebusiness dot com) */ -public class FormulaParser { +public final class FormulaParser { + /** + * Specific exception thrown when a supplied formula does not parse properly.
    + * Primarily used by test cases when testing for specific parsing exceptions.

    + * + */ + static final class FormulaParseException extends RuntimeException { + // This class was given package scope until it would become clear that it is useful to + // general client code. + public FormulaParseException(String msg) { + super(msg); + } + } + public static int FORMULA_TYPE_CELL = 0; public static int FORMULA_TYPE_SHARED = 1; public static int FORMULA_TYPE_ARRAY =2; public static int FORMULA_TYPE_CONDFOMRAT = 3; public static int FORMULA_TYPE_NAMEDRANGE = 4; - - private String formulaString; - private int pointer=0; - private int formulaLength; - - private List tokens = new java.util.Stack(); - - /** - * Using an unsynchronized linkedlist to implement a stack since we're not multi-threaded. - */ - private List functionTokens = new LinkedList(); + + private final String formulaString; + private final int formulaLength; + private int pointer; + + private final List tokens = new Stack(); /** * Used for spotting if we have a cell reference, * or a named range */ private final static Pattern CELL_REFERENCE_PATTERN = Pattern.compile("(?:('?)[^:\\\\/\\?\\*\\[\\]]+\\1!)?\\$?[A-Za-z]+\\$?[\\d]+"); - + private static char TAB = '\t'; - private static char CR = '\n'; - - private char look; // Lookahead Character - - private Workbook book; - - - /** + + /** + * Lookahead Character. + * gets value '\0' when the input string is exhausted + */ + private char look; + + private Workbook book; + + + /** * Create the formula parser, with the string that is to be * parsed against the supplied workbook. * A later call the parse() method to return ptg list in * rpn order, then call the getRPNPtg() to retrive the * parse results. * This class is recommended only for single threaded use. - * + * * If you only have a usermodel.HSSFWorkbook, and not a * model.Workbook, then use the convenience method on - * usermodel.HSSFFormulaEvaluator + * usermodel.HSSFFormulaEvaluator */ public FormulaParser(String formula, Workbook book){ formulaString = formula; pointer=0; this.book = book; - formulaLength = formulaString.length(); + formulaLength = formulaString.length(); + } + + public static Ptg[] parse(String formula, Workbook book) { + FormulaParser fp = new FormulaParser(formula, book); + fp.parse(); + return fp.getRPNPtg(); } - /** Read New Character From Input Stream */ private void GetChar() { // Check to see if we've walked off the end of the string. - // Just return if so and reset Look to smoething to keep - // SkipWhitespace from spinning - if (pointer == formulaLength) { + if (pointer > formulaLength) { + throw new RuntimeException("too far"); + } + if (pointer < formulaLength) { + look=formulaString.charAt(pointer); + } else { + // Just return if so and reset 'look' to something to keep + // SkipWhitespace from spinning look = (char)0; - return; - } - look=formulaString.charAt(pointer++); + } + pointer++; //System.out.println("Got char: "+ look); } - - - /** Report an Error */ - private void Error(String s) { - System.out.println("Error: "+s); - } - - - - /** Report Error and Halt */ - private void Abort(String s) { - Error(s); - //System.exit(1); //throw exception?? - throw new RuntimeException("Cannot Parse, sorry : " + s + " @ " + pointer + " [Formula String was: '" + formulaString + "']"); - } - - /** Report What Was Expected */ - private void Expected(String s) { - Abort(s + " Expected"); + private RuntimeException expected(String s) { + return new FormulaParseException(s + " Expected"); } - - - + + + /** Recognize an Alpha Character */ private boolean IsAlpha(char c) { return Character.isLetter(c) || c == '$' || c=='_'; } - - - + + + /** Recognize a Decimal Digit */ private boolean IsDigit(char c) { //System.out.println("Checking digit for"+c); return Character.isDigit(c); } - - + + /** Recognize an Alphanumeric */ private boolean IsAlNum(char c) { return (IsAlpha(c) || IsDigit(c)); } - - - /** Recognize an Addop */ - private boolean IsAddop( char c) { - return (c =='+' || c =='-'); - } - /** Recognize White Space */ private boolean IsWhite( char c) { return (c ==' ' || c== TAB); } - - /** - * Determines special characters;primarily in use for definition of string literals - * @param c - * @return boolean - */ - private boolean IsSpecialChar(char c) { - return (c == '>' || c== '<' || c== '=' || c=='&' || c=='[' || c==']'); - } - /** Skip Over Leading White Space */ private void SkipWhite() { @@ -187,108 +170,84 @@ public class FormulaParser { GetChar(); } } - - - /** Match a Specific Input Character */ + /** + * Consumes the next input character if it is equal to the one specified otherwise throws an + * unchecked exception. This method does not consume whitespace (before or after the + * matched character). + */ private void Match(char x) { if (look != x) { - Expected("" + x + ""); - }else { - GetChar(); - SkipWhite(); + throw expected("'" + x + "'"); } + GetChar(); } - + /** Get an Identifier */ private String GetName() { StringBuffer Token = new StringBuffer(); if (!IsAlpha(look) && look != '\'') { - Expected("Name"); + throw expected("Name"); } if(look == '\'') { - Match('\''); - boolean done = look == '\''; - while(!done) - { - Token.append(Character.toUpperCase(look)); - GetChar(); - if(look == '\'') - { - Match('\''); - done = look != '\''; - } - } + Match('\''); + boolean done = look == '\''; + while(!done) + { + Token.append(look); + GetChar(); + if(look == '\'') + { + Match('\''); + done = look != '\''; + } + } } else { - while (IsAlNum(look)) { - Token.append(Character.toUpperCase(look)); - GetChar(); - } - } - SkipWhite(); - return Token.toString(); - } - - /**Get an Identifier AS IS, without stripping white spaces or - converting to uppercase; used for literals */ - private String GetNameAsIs() { - StringBuffer Token = new StringBuffer(); - - while (IsAlNum(look) || IsWhite(look) || IsSpecialChar(look)) { - Token = Token.append(look); - GetChar(); + while (IsAlNum(look)) { + Token.append(look); + GetChar(); + } } return Token.toString(); } - - + + /** Get a Number */ private String GetNum() { StringBuffer value = new StringBuffer(); - + while (IsDigit(this.look)){ value.append(this.look); GetChar(); } - - SkipWhite(); - return value.length() == 0 ? null : value.toString(); } - - - /** Output a String with Tab */ - private void Emit(String s){ - System.out.print(TAB+s); - } - /** Output a String with Tab and CRLF */ - private void EmitLn(String s) { - Emit(s); - System.out.println();; - } - /** Parse and Translate a String Identifier */ - private void Ident() { + private Ptg parseIdent() { String name; name = GetName(); if (look == '('){ //This is a function - function(name); - } else if (look == ':' || look == '.') { // this is a AreaReference + return function(name); + } + + if (look == ':' || look == '.') { // this is a AreaReference GetChar(); - + while (look == '.') { // formulas can have . or .. or ... instead of : GetChar(); } - + String first = name; String second = GetName(); - tokens.add(new AreaPtg(first+":"+second)); - } else if (look == '!') { + return new AreaPtg(first+":"+second); + } + + if (look == '!') { Match('!'); String sheetName = name; String first = GetName(); @@ -297,79 +256,78 @@ public class FormulaParser { Match(':'); String second=GetName(); if (look == '!') { - //The sheet name was included in both of the areas. Only really - //need it once - Match('!'); - String third=GetName(); - - if (!sheetName.equals(second)) - throw new RuntimeException("Unhandled double sheet reference."); - - tokens.add(new Area3DPtg(first+":"+third,externIdx)); - } else { - tokens.add(new Area3DPtg(first+":"+second,externIdx)); + //The sheet name was included in both of the areas. Only really + //need it once + Match('!'); + String third=GetName(); + + if (!sheetName.equals(second)) + throw new RuntimeException("Unhandled double sheet reference."); + + return new Area3DPtg(first+":"+third,externIdx); } - } else { - tokens.add(new Ref3DPtg(first,externIdx)); + return new Area3DPtg(first+":"+second,externIdx); } - } else { - // This can be either a cell ref or a named range - // Try to spot which it is - boolean cellRef = CELL_REFERENCE_PATTERN.matcher(name).matches(); - boolean boolLit = (name.equals("TRUE") || name.equals("FALSE")); - - if (boolLit) { - tokens.add(new BoolPtg(name)); - } else if (cellRef) { - tokens.add(new ReferencePtg(name)); - } else { - boolean nameRecordExists = false; - for(int i = 0; i < book.getNumNames(); i++) { - // Our formula will by now contain an upper-cased - // version of any named range names - if(book.getNameRecord(i).getNameText().toUpperCase().equals(name)) { - nameRecordExists = true; - } - } - if(!nameRecordExists) - Abort("Found reference to named range \"" + name + "\", but that named range wasn't defined!"); - tokens.add(new NamePtg(name, book)); + return new Ref3DPtg(first,externIdx); + } + if (name.equalsIgnoreCase("TRUE") || name.equalsIgnoreCase("FALSE")) { + return new BoolPtg(name.toUpperCase()); + } + + // This can be either a cell ref or a named range + // Try to spot which it is + boolean cellRef = CELL_REFERENCE_PATTERN.matcher(name).matches(); + + if (cellRef) { + return new ReferencePtg(name); + } + + for(int i = 0; i < book.getNumNames(); i++) { + // named range name matching is case insensitive + if(book.getNameRecord(i).getNameText().equalsIgnoreCase(name)) { + return new NamePtg(name, book); } } + throw new FormulaParseException("Found reference to named range \"" + + name + "\", but that named range wasn't defined!"); } - + /** * Adds a pointer to the last token to the latest function argument list. * @param obj */ - private void addArgumentPointer() { - if (this.functionTokens.size() > 0) { - //no bounds check because this method should not be called unless a token array is setup by function() - List arguments = (List)this.functionTokens.get(0); - arguments.add(tokens.get(tokens.size()-1)); - } + private void addArgumentPointer(List argumentPointers) { + argumentPointers.add(tokens.get(tokens.size()-1)); } - - private void function(String name) { - //average 2 args per function - this.functionTokens.add(0, new ArrayList(2)); - - Match('('); - int numArgs = Arguments(); - Match(')'); - - AbstractFunctionPtg functionPtg = getFunction(name,(byte)numArgs); - - tokens.add(functionPtg); - - if (functionPtg.getName().equals("externalflag")) { - tokens.add(new NamePtg(name, this.book)); - } - //remove what we just put in - this.functionTokens.remove(0); + /** + * Note - Excel function names are 'case aware but not case sensitive'. This method may end + * up creating a defined name record in the workbook if the specified name is not an internal + * Excel function, and has not been encountered before. + * + * @param name case preserved function name (as it was entered/appeared in the formula). + */ + private Ptg function(String name) { + int numArgs =0 ; + // Note regarding parameter - + if(!AbstractFunctionPtg.isInternalFunctionName(name)) { + // external functions get a Name token which points to a defined name record + NamePtg nameToken = new NamePtg(name, this.book); + + // in the token tree, the name is more or less the first argument + numArgs++; + tokens.add(nameToken); + } + //average 2 args per function + List argumentPointers = new ArrayList(2); + + Match('('); + numArgs += Arguments(argumentPointers); + Match(')'); + + return getFunction(name, numArgs, argumentPointers); } - + /** * Adds the size of all the ptgs after the provided index (inclusive). *

    @@ -378,17 +336,17 @@ public class FormulaParser { * @return int */ private int getPtgSize(int index) { - int count = 0; - - Iterator ptgIterator = tokens.listIterator(index); - while (ptgIterator.hasNext()) { - Ptg ptg = (Ptg)ptgIterator.next(); - count+=ptg.getSize(); - } - - return count; + int count = 0; + + Iterator ptgIterator = tokens.listIterator(index); + while (ptgIterator.hasNext()) { + Ptg ptg = (Ptg)ptgIterator.next(); + count+=ptg.getSize(); + } + + return count; } - + private int getPtgSize(int start, int end) { int count = 0; int index = start; @@ -398,390 +356,430 @@ public class FormulaParser { count+=ptg.getSize(); index++; } - + return count; } /** * Generates the variable function ptg for the formula. *

    - * For IF Formulas, additional PTGs are added to the tokens + * For IF Formulas, additional PTGs are added to the tokens * @param name * @param numArgs * @return Ptg a null is returned if we're in an IF formula, it needs extreme manipulation and is handled in this function */ - private AbstractFunctionPtg getFunction(String name, byte numArgs) { - AbstractFunctionPtg retval = null; - - if (name.equals("IF")) { - retval = new FuncVarPtg(AbstractFunctionPtg.ATTR_NAME, numArgs); - - //simulated pop, no bounds checking because this list better be populated by function() - List argumentPointers = (List)this.functionTokens.get(0); - - - AttrPtg ifPtg = new AttrPtg(); - ifPtg.setData((short)7); //mirroring excel output - ifPtg.setOptimizedIf(true); - - if (argumentPointers.size() != 2 && argumentPointers.size() != 3) { - throw new IllegalArgumentException("["+argumentPointers.size()+"] Arguments Found - An IF formula requires 2 or 3 arguments. IF(CONDITION, TRUE_VALUE, FALSE_VALUE [OPTIONAL]"); - } - - //Biffview of an IF formula record indicates the attr ptg goes after the condition ptgs and are - //tracked in the argument pointers - //The beginning first argument pointer is the last ptg of the condition - int ifIndex = tokens.indexOf(argumentPointers.get(0))+1; - tokens.add(ifIndex, ifPtg); - - //we now need a goto ptgAttr to skip to the end of the formula after a true condition - //the true condition is should be inserted after the last ptg in the first argument - - int gotoIndex = tokens.indexOf(argumentPointers.get(1))+1; - - AttrPtg goto1Ptg = new AttrPtg(); - goto1Ptg.setGoto(true); - - - tokens.add(gotoIndex, goto1Ptg); - - - if (numArgs > 2) { //only add false jump if there is a false condition - - //second goto to skip past the function ptg - AttrPtg goto2Ptg = new AttrPtg(); - goto2Ptg.setGoto(true); - goto2Ptg.setData((short)(retval.getSize()-1)); - //Page 472 of the Microsoft Excel Developer's kit states that: - //The b(or w) field specifies the number byes (or words to skip, minus 1 - - tokens.add(goto2Ptg); //this goes after all the arguments are defined - } - - //data portion of the if ptg points to the false subexpression (Page 472 of MS Excel Developer's kit) - //count the number of bytes after the ifPtg to the False Subexpression - //doesn't specify -1 in the documentation - ifPtg.setData((short)(getPtgSize(ifIndex+1, gotoIndex))); - - //count all the additional (goto) ptgs but dont count itself - int ptgCount = this.getPtgSize(gotoIndex)-goto1Ptg.getSize()+retval.getSize(); - if (ptgCount > (int)Short.MAX_VALUE) { - throw new RuntimeException("Ptg Size exceeds short when being specified for a goto ptg in an if"); - } - - goto1Ptg.setData((short)(ptgCount-1)); - - } else { - - retval = new FuncVarPtg(name,numArgs); + private AbstractFunctionPtg getFunction(String name, int numArgs, List argumentPointers) { + + AbstractFunctionPtg retval = new FuncVarPtg(name, (byte)numArgs); + if (!name.equals(AbstractFunctionPtg.FUNCTION_NAME_IF)) { + // early return for everything else besides IF() + return retval; } - + + + AttrPtg ifPtg = new AttrPtg(); + ifPtg.setData((short)7); //mirroring excel output + ifPtg.setOptimizedIf(true); + + if (argumentPointers.size() != 2 && argumentPointers.size() != 3) { + throw new IllegalArgumentException("["+argumentPointers.size()+"] Arguments Found - An IF formula requires 2 or 3 arguments. IF(CONDITION, TRUE_VALUE, FALSE_VALUE [OPTIONAL]"); + } + + //Biffview of an IF formula record indicates the attr ptg goes after the condition ptgs and are + //tracked in the argument pointers + //The beginning first argument pointer is the last ptg of the condition + int ifIndex = tokens.indexOf(argumentPointers.get(0))+1; + tokens.add(ifIndex, ifPtg); + + //we now need a goto ptgAttr to skip to the end of the formula after a true condition + //the true condition is should be inserted after the last ptg in the first argument + + int gotoIndex = tokens.indexOf(argumentPointers.get(1))+1; + + AttrPtg goto1Ptg = new AttrPtg(); + goto1Ptg.setGoto(true); + + + tokens.add(gotoIndex, goto1Ptg); + + + if (numArgs > 2) { //only add false jump if there is a false condition + + //second goto to skip past the function ptg + AttrPtg goto2Ptg = new AttrPtg(); + goto2Ptg.setGoto(true); + goto2Ptg.setData((short)(retval.getSize()-1)); + //Page 472 of the Microsoft Excel Developer's kit states that: + //The b(or w) field specifies the number byes (or words to skip, minus 1 + + tokens.add(goto2Ptg); //this goes after all the arguments are defined + } + + //data portion of the if ptg points to the false subexpression (Page 472 of MS Excel Developer's kit) + //count the number of bytes after the ifPtg to the False Subexpression + //doesn't specify -1 in the documentation + ifPtg.setData((short)(getPtgSize(ifIndex+1, gotoIndex))); + + //count all the additional (goto) ptgs but dont count itself + int ptgCount = this.getPtgSize(gotoIndex)-goto1Ptg.getSize()+retval.getSize(); + if (ptgCount > Short.MAX_VALUE) { + throw new RuntimeException("Ptg Size exceeds short when being specified for a goto ptg in an if"); + } + + goto1Ptg.setData((short)(ptgCount-1)); + return retval; } + + private static boolean isArgumentDelimiter(char ch) { + return ch == ',' || ch == ')'; + } /** get arguments to a function */ - private int Arguments() { - int numArgs = 0; - if (look != ')') { - numArgs++; - Expression(); - addArgumentPointer(); + private int Arguments(List argumentPointers) { + SkipWhite(); + if(look == ')') { + return 0; } - while (look == ',' || look == ';') { //TODO handle EmptyArgs - if(look == ',') { - Match(','); + + boolean missedPrevArg = true; + + int numArgs = 0; + while(true) { + SkipWhite(); + if(isArgumentDelimiter(look)) { + if(missedPrevArg) { + tokens.add(new MissingArgPtg()); + addArgumentPointer(argumentPointers); + numArgs++; + } + if(look == ')') { + break; + } + Match(','); + missedPrevArg = true; + continue; } - else { - Match(';'); - } - Expression(); - addArgumentPointer(); + comparisonExpression(); + addArgumentPointer(argumentPointers); numArgs++; + missedPrevArg = false; } return numArgs; } /** Parse and Translate a Math Factor */ - private void Factor() { - if (look == '-') - { - Match('-'); - Factor(); - tokens.add(new UnaryMinusPtg()); - } - else if (look == '+') { - Match('+'); - Factor(); - tokens.add(new UnaryPlusPtg()); - } - else if (look == '(' ) { - Match('('); - Expression(); - Match(')'); - tokens.add(new ParenthesisPtg()); - } else if (IsAlpha(look) || look == '\''){ - Ident(); - } else if(look == '"') { - StringLiteral(); - } else if (look == ')' || look == ',') { - tokens.add(new MissingArgPtg()); - } else { - String number2 = null; - String exponent = null; - String number1 = GetNum(); - - if (look == '.') { - GetChar(); - number2 = GetNum(); + private void powerFactor() { + percentFactor(); + while(true) { + SkipWhite(); + if(look != '^') { + return; } - - if (look == 'E') { - GetChar(); - - String sign = ""; - if (look == '+') { - GetChar(); - } else if (look == '-') { - GetChar(); - sign = "-"; - } - - String number = GetNum(); - if (number == null) { - Expected("Integer"); - } - exponent = sign + number; - } - - if (number1 == null && number2 == null) { - Expected("Integer"); - } - - tokens.add(getNumberPtgFromString(number1, number2, exponent)); + Match('^'); + percentFactor(); + tokens.add(new PowerPtg()); } } - /** - * Get a PTG for an integer from its string representation. - * return Int or Number Ptg based on size of input - */ - private Ptg getNumberPtgFromString(String number1, String number2, String exponent) { + private void percentFactor() { + tokens.add(parseSimpleFactor()); + while(true) { + SkipWhite(); + if(look != '%') { + return; + } + Match('%'); + tokens.add(new PercentPtg()); + } + } + + + /** + * factors (without ^ or % ) + */ + private Ptg parseSimpleFactor() { + SkipWhite(); + switch(look) { + case '#': + return parseErrorLiteral(); + case '-': + Match('-'); + powerFactor(); + return new UnaryMinusPtg(); + case '+': + Match('+'); + powerFactor(); + return new UnaryPlusPtg(); + case '(': + Match('('); + comparisonExpression(); + Match(')'); + return new ParenthesisPtg(); + case '"': + return parseStringLiteral(); + case ',': + case ')': + return new MissingArgPtg(); // TODO - not quite the right place to recognise a missing arg + } + if (IsAlpha(look) || look == '\''){ + return parseIdent(); + } + // else - assume number + return parseNumber(); + } + + + private Ptg parseNumber() { + String number2 = null; + String exponent = null; + String number1 = GetNum(); + + if (look == '.') { + GetChar(); + number2 = GetNum(); + } + + if (look == 'E') { + GetChar(); + + String sign = ""; + if (look == '+') { + GetChar(); + } else if (look == '-') { + GetChar(); + sign = "-"; + } + + String number = GetNum(); + if (number == null) { + throw expected("Integer"); + } + exponent = sign + number; + } + + if (number1 == null && number2 == null) { + throw expected("Integer"); + } + + return getNumberPtgFromString(number1, number2, exponent); + } + + + private ErrPtg parseErrorLiteral() { + Match('#'); + String part1 = GetName().toUpperCase(); + + switch(part1.charAt(0)) { + case 'V': + if(part1.equals("VALUE")) { + Match('!'); + return ErrPtg.VALUE_INVALID; + } + throw expected("#VALUE!"); + case 'R': + if(part1.equals("REF")) { + Match('!'); + return ErrPtg.REF_INVALID; + } + throw expected("#REF!"); + case 'D': + if(part1.equals("DIV")) { + Match('/'); + Match('0'); + Match('!'); + return ErrPtg.DIV_ZERO; + } + throw expected("#DIV/0!"); + case 'N': + if(part1.equals("NAME")) { + Match('?'); // only one that ends in '?' + return ErrPtg.NAME_INVALID; + } + if(part1.equals("NUM")) { + Match('!'); + return ErrPtg.NUM_ERROR; + } + if(part1.equals("NULL")) { + Match('!'); + return ErrPtg.NULL_INTERSECTION; + } + if(part1.equals("N")) { + Match('/'); + if(look != 'A' && look != 'a') { + throw expected("#N/A"); + } + Match(look); + // Note - no '!' or '?' suffix + return ErrPtg.N_A; + } + throw expected("#NAME?, #NUM!, #NULL! or #N/A"); + + } + throw expected("#VALUE!, #REF!, #DIV/0!, #NAME?, #NUM!, #NULL! or #N/A"); + } + + + /** + * Get a PTG for an integer from its string representation. + * return Int or Number Ptg based on size of input + */ + private static Ptg getNumberPtgFromString(String number1, String number2, String exponent) { StringBuffer number = new StringBuffer(); - - if (number2 == null) { - number.append(number1); - - if (exponent != null) { - number.append('E'); - number.append(exponent); - } - - String numberStr = number.toString(); - - try { - return new IntPtg(numberStr); - } catch (NumberFormatException e) { - return new NumberPtg(numberStr); - } - } else { - if (number1 != null) { - number.append(number1); - } - - number.append('.'); - number.append(number2); - + + if (number2 == null) { + number.append(number1); + if (exponent != null) { number.append('E'); number.append(exponent); } - - return new NumberPtg(number.toString()); - } - } - - - private void StringLiteral() - { - // Can't use match here 'cuz it consumes whitespace - // which we need to preserve inside the string. - // - pete - // Match('"'); - if (look != '"') - Expected("\""); - else - { - GetChar(); - StringBuffer Token = new StringBuffer(); - for (;;) - { - if (look == '"') - { - GetChar(); - SkipWhite(); //potential white space here since it doesnt matter up to the operator - if (look == '"') - Token.append("\""); - else - break; - } - else if (look == 0) - { - break; - } - else - { - Token.append(look); - GetChar(); - } - } - tokens.add(new StringPtg(Token.toString())); - } - } - - /** Recognize and Translate a Multiply */ - private void Multiply(){ - Match('*'); - Factor(); - tokens.add(new MultiplyPtg()); - - } - - - /** Recognize and Translate a Divide */ - private void Divide() { - Match('/'); - Factor(); - tokens.add(new DividePtg()); + String numberStr = number.toString(); + int intVal; + try { + intVal = Integer.parseInt(numberStr); + } catch (NumberFormatException e) { + return new NumberPtg(numberStr); + } + if (IntPtg.isInRange(intVal)) { + return new IntPtg(intVal); + } + return new NumberPtg(numberStr); + } + + if (number1 != null) { + number.append(number1); + } + + number.append('.'); + number.append(number2); + + if (exponent != null) { + number.append('E'); + number.append(exponent); + } + + return new NumberPtg(number.toString()); } - - + + + private StringPtg parseStringLiteral() + { + Match('"'); + + StringBuffer token = new StringBuffer(); + while (true) { + if (look == '"') { + GetChar(); + if (look != '"') { + break; + } + } + token.append(look); + GetChar(); + } + return new StringPtg(token.toString()); + } + /** Parse and Translate a Math Term */ - private void Term(){ - Factor(); - while (look == '*' || look == '/' || look == '^' || look == '&') { - - ///TODO do we need to do anything here?? - if (look == '*') Multiply(); - else if (look == '/') Divide(); - else if (look == '^') Power(); - else if (look == '&') Concat(); + private void Term() { + powerFactor(); + while(true) { + SkipWhite(); + switch(look) { + case '*': + Match('*'); + powerFactor(); + tokens.add(new MultiplyPtg()); + continue; + case '/': + Match('/'); + powerFactor(); + tokens.add(new DividePtg()); + continue; + } + return; // finished with Term } } - /** Recognize and Translate an Add */ - private void Add() { - Match('+'); - Term(); - tokens.add(new AddPtg()); + private void comparisonExpression() { + concatExpression(); + while (true) { + SkipWhite(); + switch(look) { + case '=': + case '>': + case '<': + Ptg comparisonToken = getComparisonToken(); + concatExpression(); + tokens.add(comparisonToken); + continue; + } + return; // finished with predicate expression + } } - - /** Recognize and Translate a Concatination */ - private void Concat() { - Match('&'); - Term(); - tokens.add(new ConcatPtg()); - } - - /** Recognize and Translate a test for Equality */ - private void Equal() { - Match('='); - Expression(); - tokens.add(new EqualPtg()); - } - - /** Recognize and Translate a Subtract */ - private void Subtract() { - Match('-'); - Term(); - tokens.add(new SubtractPtg()); - } - private void Power() { - Match('^'); - Term(); - tokens.add(new PowerPtg()); + private Ptg getComparisonToken() { + if(look == '=') { + Match(look); + return new EqualPtg(); + } + boolean isGreater = look == '>'; + Match(look); + if(isGreater) { + if(look == '=') { + Match('='); + return new GreaterEqualPtg(); + } + return new GreaterThanPtg(); + } + switch(look) { + case '=': + Match('='); + return new LessEqualPtg(); + case '>': + Match('>'); + return new NotEqualPtg(); + } + return new LessThanPtg(); } + + private void concatExpression() { + additiveExpression(); + while (true) { + SkipWhite(); + if(look != '&') { + break; // finished with concat expression + } + Match('&'); + additiveExpression(); + tokens.add(new ConcatPtg()); + } + } + /** Parse and Translate an Expression */ - private void Expression() { + private void additiveExpression() { Term(); - while (IsAddop(look)) { - if (look == '+' ) Add(); - else if (look == '-') Subtract(); + while (true) { + SkipWhite(); + switch(look) { + case '+': + Match('+'); + Term(); + tokens.add(new AddPtg()); + continue; + case '-': + Match('-'); + Term(); + tokens.add(new SubtractPtg()); + continue; + } + return; // finished with additive expression } - - /* - * This isn't quite right since it would allow multiple comparison operators. - */ - - if(look == '=' || look == '>' || look == '<') { - if (look == '=') Equal(); - else if (look == '>') GreaterThan(); - else if (look == '<') LessThan(); - return; - } - - } - - /** Recognize and Translate a Greater Than */ - private void GreaterThan() { - Match('>'); - if(look == '=') - GreaterEqual(); - else { - Expression(); - tokens.add(new GreaterThanPtg()); - } - } - - /** Recognize and Translate a Less Than */ - private void LessThan() { - Match('<'); - if(look == '=') - LessEqual(); - else if(look == '>') - NotEqual(); - else { - Expression(); - tokens.add(new LessThanPtg()); - } - } - - /** - * Recognize and translate Greater than or Equal - * - */ - private void GreaterEqual() { - Match('='); - Expression(); - tokens.add(new GreaterEqualPtg()); - } - - /** - * Recognize and translate Less than or Equal - * - */ - - private void LessEqual() { - Match('='); - Expression(); - tokens.add(new LessEqualPtg()); - } - - /** - * Recognize and not Equal - * - */ - - private void NotEqual() { - Match('>'); - Expression(); - tokens.add(new NotEqualPtg()); - } - //{--------------------------------------------------------------} //{ Parse and Translate an Assignment Statement } /** @@ -794,48 +792,46 @@ begin end; **/ - - - /** Initialize */ - - private void init() { - GetChar(); - SkipWhite(); - } - + + /** API call to execute the parsing of the formula * */ public void parse() { - synchronized (tokens) { - init(); - Expression(); + pointer=0; + GetChar(); + comparisonExpression(); + + if(pointer <= formulaLength) { + String msg = "Unused input [" + formulaString.substring(pointer-1) + + "] after attempting to parse the formula [" + formulaString + "]"; + throw new FormulaParseException(msg); } } - - + + /********************************* * PARSER IMPLEMENTATION ENDS HERE * EXCEL SPECIFIC METHODS BELOW *******************************/ - - /** API call to retrive the array of Ptgs created as + + /** API call to retrive the array of Ptgs created as * a result of the parsing */ public Ptg[] getRPNPtg() { return getRPNPtg(FORMULA_TYPE_CELL); } - + public Ptg[] getRPNPtg(int formulaType) { Node node = createTree(); setRootLevelRVA(node, formulaType); setParameterRVA(node,formulaType); return (Ptg[]) tokens.toArray(new Ptg[0]); } - + private void setRootLevelRVA(Node n, int formulaType) { //Pg 16, excelfileformat.pdf @ openoffice.org - Ptg p = (Ptg) n.getValue(); + Ptg p = n.getValue(); if (formulaType == FormulaParser.FORMULA_TYPE_NAMEDRANGE) { if (p.getDefaultOperandClass() == Ptg.CLASS_REF) { setClass(n,Ptg.CLASS_REF); @@ -845,9 +841,9 @@ end; } else { setClass(n,Ptg.CLASS_VALUE); } - + } - + private void setParameterRVA(Node n, int formulaType) { Ptg p = n.getValue(); int numOperands = n.getNumChildren(); @@ -863,11 +859,11 @@ end; for (int i =0;i 0; j--) { - //TODO: catch stack underflow and throw parse exception. - operands[j - 1] = (String) stack.pop(); - } - - stack.push(o.toFormulaString(operands)); - if (!(o instanceof AbstractFunctionPtg)) continue; - - final AbstractFunctionPtg f = (AbstractFunctionPtg) o; - final String fname = f.getName(); - if (fname == null) continue; - - if ((ifptg != null) && (fname.equals("specialflag"))) { - // this special case will be way different. - stack.push(ifptg.toFormulaString(new String[]{(String) stack.pop()})); - continue; - } - if (fname.equals("externalflag")) { - final String top = (String) stack.pop(); - final int paren = top.indexOf('('); - final int comma = top.indexOf(','); - if (comma == -1) { - final int rparen = top.indexOf(')'); - stack.push(top.substring(paren + 1, rparen) + "()"); - } - else { - stack.push(top.substring(paren + 1, comma) + '(' + - top.substring(comma + 1)); + int i; + if(ptgs[0] instanceof AttrPtg) { + AttrPtg attrPtg0 = (AttrPtg) ptgs[0]; + if(attrPtg0.isSemiVolatile()) { + // no visible formula for semi-volatile + } else { + // TODO -this requirement is unclear and is not addressed by any junits + stack.push(ptgs[0].toFormulaString(book)); } + i=1; + } else { + i=0; } - } - // TODO: catch stack underflow and throw parse exception. - return (String) stack.pop(); + + for ( ; i < ptgs.length; i++) { + Ptg ptg = ptgs[i]; + // TODO - what about MemNoMemPtg? + if(ptg instanceof MemAreaPtg || ptg instanceof MemFuncPtg || ptg instanceof MemErrPtg) { + // marks the start of a list of area expressions which will be naturally combined + // by their trailing operators (e.g. UnionPtg) + // TODO - put comment and throw exception in toFormulaString() of these classes + continue; + } + if (! (ptg instanceof OperationPtg)) { + stack.push(ptg.toFormulaString(book)); + continue; + } + + if (ptg instanceof AttrPtg && ((AttrPtg) ptg).isOptimizedIf()) { + continue; + } + + final OperationPtg o = (OperationPtg) ptg; + int nOperands = o.getNumberOfOperands(); + final String[] operands = new String[nOperands]; + + for (int j = nOperands-1; j >= 0; j--) { + if(stack.isEmpty()) { + //TODO: write junit to prove this works + String msg = "Too few arguments suppled to operation token (" + + o.getClass().getName() + "). Expected (" + nOperands + + " but got " + (nOperands - j + 1); + throw new FormulaParseException(msg); + } + operands[j] = (String) stack.pop(); + } + stack.push(o.toFormulaString(operands)); + } + if(stack.isEmpty()) { + // inspection of the code above reveals that every stack.pop() is followed by a + // stack.push(). So this is either an internal error or impossible. + throw new IllegalStateException("Stack underflow"); + } + String result = (String) stack.pop(); + if(!stack.isEmpty()) { + // Might be caused by some tokens like AttrPtg and Mem*Ptg, which really shouldn't + // put anything on the stack + throw new IllegalStateException("too much stuff left on the stack"); + } + return result; } /** * Static method to convert an array of Ptgs in RPN order @@ -1005,7 +1014,7 @@ end; * @return a human readable String */ public String toFormulaString(Ptg[] ptgs) { - return toFormulaString(book, ptgs); + return toFormulaString(book, ptgs); } @@ -1013,19 +1022,19 @@ end; *used to run the class(RVA) change algo */ private Node createTree() { - java.util.Stack stack = new java.util.Stack(); + Stack stack = new Stack(); int numPtgs = tokens.size(); OperationPtg o; int numOperands; Node[] operands; for (int i=0;i + * + * The main data of all types of references is stored in the Link Table inside the Workbook Globals + * Substream (4.2.5). The Link Table itself is optional and occurs only, if there are any + * references in the document. + *

    + * + * In BIFF8 the Link Table consists of + *

      + *
    • one or more EXTERNALBOOK Blocks

      + * each consisting of + *

        + *
      • exactly one EXTERNALBOOK (0x01AE) record
      • + *
      • zero or more EXTERNALNAME (0x0023) records
      • + *
      • zero or more CRN Blocks

        + * each consisting of + *

          + *
        • exactly one XCT (0x0059)record
        • + *
        • zero or more CRN (0x005A) records (documentation says one or more)
        • + *
        + *
      • + *
      + *
    • + *
    • exactly one EXTERNSHEET (0x0017) record
    • + *
    • zero or more DEFINEDNAME (0x0018) records
    • + *
    + * + * + * @author Josh Micich + */ +final class LinkTable { + + private static final class CRNBlock { + + private final CRNCountRecord _countRecord; + private final CRNRecord[] _crns; + + public CRNBlock(RecordStream rs) { + _countRecord = (CRNCountRecord) rs.getNext(); + int nCRNs = _countRecord.getNumberOfCRNs(); + CRNRecord[] crns = new CRNRecord[nCRNs]; + for (int i = 0; i < crns.length; i++) { + crns[i] = (CRNRecord) rs.getNext(); + } + _crns = crns; + } + public CRNRecord[] getCrns() { + return (CRNRecord[]) _crns.clone(); + } + } + + private static final class ExternalBookBlock { + private final SupBookRecord _externalBookRecord; + private final ExternalNameRecord[] _externalNameRecords; + private final CRNBlock[] _crnBlocks; + + public ExternalBookBlock(RecordStream rs) { + _externalBookRecord = (SupBookRecord) rs.getNext(); + List temp = new ArrayList(); + while(rs.peekNextClass() == ExternalNameRecord.class) { + temp.add(rs.getNext()); + } + _externalNameRecords = new ExternalNameRecord[temp.size()]; + temp.toArray(_externalNameRecords); + + temp.clear(); + + while(rs.peekNextClass() == CRNCountRecord.class) { + temp.add(new CRNBlock(rs)); + } + _crnBlocks = new CRNBlock[temp.size()]; + temp.toArray(_crnBlocks); + } + + public ExternalBookBlock(short numberOfSheets) { + _externalBookRecord = SupBookRecord.createInternalReferences(numberOfSheets); + _externalNameRecords = new ExternalNameRecord[0]; + _crnBlocks = new CRNBlock[0]; + } + + public SupBookRecord getExternalBookRecord() { + return _externalBookRecord; + } + + public String getNameText(int definedNameIndex) { + return _externalNameRecords[definedNameIndex].getText(); + } + } + + private final ExternalBookBlock[] _externalBookBlocks; + private final ExternSheetRecord _externSheetRecord; + private final List _definedNames; + private final int _recordCount; + private final WorkbookRecordList _workbookRecordList; // TODO - would be nice to remove this + + public LinkTable(List inputList, int startIndex, WorkbookRecordList workbookRecordList) { + + _workbookRecordList = workbookRecordList; + RecordStream rs = new RecordStream(inputList, startIndex); + + List temp = new ArrayList(); + while(rs.peekNextClass() == SupBookRecord.class) { + temp.add(new ExternalBookBlock(rs)); + } + if(temp.size() < 1) { + throw new RuntimeException("Need at least one EXTERNALBOOK blocks"); + } + _externalBookBlocks = new ExternalBookBlock[temp.size()]; + temp.toArray(_externalBookBlocks); + temp.clear(); + + // If link table is present, there is always 1 of ExternSheetRecord + Record next = rs.getNext(); + _externSheetRecord = (ExternSheetRecord)next; + _definedNames = new ArrayList(); + // collect zero or more DEFINEDNAMEs id=0x18 + while(rs.peekNextClass() == NameRecord.class) { + NameRecord nr = (NameRecord)rs.getNext(); + _definedNames.add(nr); + } + + _recordCount = rs.getCountRead(); + _workbookRecordList.getRecords().addAll(inputList.subList(startIndex, startIndex + _recordCount)); + } + + public LinkTable(short numberOfSheets, WorkbookRecordList workbookRecordList) { + _workbookRecordList = workbookRecordList; + _definedNames = new ArrayList(); + _externalBookBlocks = new ExternalBookBlock[] { + new ExternalBookBlock(numberOfSheets), + }; + _externSheetRecord = new ExternSheetRecord(); + _recordCount = 2; + + // tell _workbookRecordList about the 2 new records + + SupBookRecord supbook = _externalBookBlocks[0].getExternalBookRecord(); + + int idx = findFirstRecordLocBySid(CountryRecord.sid); + if(idx < 0) { + throw new RuntimeException("CountryRecord not found"); + } + _workbookRecordList.add(idx+1, _externSheetRecord); + _workbookRecordList.add(idx+1, supbook); + } + + /** + * TODO - would not be required if calling code used RecordStream or similar + */ + public int getRecordCount() { + return _recordCount; + } + + + public NameRecord getSpecificBuiltinRecord(byte name, int sheetIndex) { + + Iterator iterator = _definedNames.iterator(); + while (iterator.hasNext()) { + NameRecord record = ( NameRecord ) iterator.next(); + + //print areas are one based + if (record.getBuiltInName() == name && record.getIndexToSheet() == sheetIndex) { + return record; + } + } + + return null; + } + + public void removeBuiltinRecord(byte name, int sheetIndex) { + //the name array is smaller so searching through it should be faster than + //using the findFirstXXXX methods + NameRecord record = getSpecificBuiltinRecord(name, sheetIndex); + if (record != null) { + _definedNames.remove(record); + } + // TODO - do we need "Workbook.records.remove(...);" similar to that in Workbook.removeName(int namenum) {}? + } + + public int getNumNames() { + return _definedNames.size(); + } + + public NameRecord getNameRecord(int index) { + return (NameRecord) _definedNames.get(index); + } + + public void addName(NameRecord name) { + _definedNames.add(name); + + // TODO - this is messy + // Not the most efficient way but the other way was causing too many bugs + int idx = findFirstRecordLocBySid(ExternSheetRecord.sid); + if (idx == -1) idx = findFirstRecordLocBySid(SupBookRecord.sid); + if (idx == -1) idx = findFirstRecordLocBySid(CountryRecord.sid); + int countNames = _definedNames.size(); + _workbookRecordList.add(idx+countNames, name); + + } + + public void removeName(int namenum) { + _definedNames.remove(namenum); + } + + public short getIndexToSheet(short num) { + return _externSheetRecord.getREFRecordAt(num).getIndexToFirstSupBook(); + } + + public int getSheetIndexFromExternSheetIndex(int externSheetNumber) { + if (externSheetNumber >= _externSheetRecord.getNumOfREFStructures()) { + return -1; + } + return _externSheetRecord.getREFRecordAt(externSheetNumber).getIndexToFirstSupBook(); + } + + public short addSheetIndexToExternSheet(short sheetNumber) { + + ExternSheetSubRecord record = new ExternSheetSubRecord(); + record.setIndexToFirstSupBook(sheetNumber); + record.setIndexToLastSupBook(sheetNumber); + _externSheetRecord.addREFRecord(record); + _externSheetRecord.setNumOfREFStructures((short)(_externSheetRecord.getNumOfREFStructures() + 1)); + return (short)(_externSheetRecord.getNumOfREFStructures() - 1); + } + + public short checkExternSheet(int sheetNumber) { + + //Trying to find reference to this sheet + int nESRs = _externSheetRecord.getNumOfREFStructures(); + for(short i=0; i< nESRs; i++) { + ExternSheetSubRecord esr = _externSheetRecord.getREFRecordAt(i); + + if (esr.getIndexToFirstSupBook() == sheetNumber + && esr.getIndexToLastSupBook() == sheetNumber){ + return i; + } + } + + //We Haven't found reference to this sheet + return addSheetIndexToExternSheet((short) sheetNumber); + } + + + /** + * copied from Workbook + */ + private int findFirstRecordLocBySid(short sid) { + int index = 0; + for (Iterator iterator = _workbookRecordList.iterator(); iterator.hasNext(); ) { + Record record = ( Record ) iterator.next(); + + if (record.getSid() == sid) { + return index; + } + index ++; + } + return -1; + } + + public int getNumberOfREFStructures() { + return _externSheetRecord.getNumOfREFStructures(); + } + + public String resolveNameXText(int refIndex, int definedNameIndex) { + short extBookIndex = _externSheetRecord.getREFRecordAt(refIndex).getIndexToSupBook(); + return _externalBookBlocks[extBookIndex].getNameText(definedNameIndex); + } +} diff --git a/src/java/org/apache/poi/hssf/model/RecordStream.java b/src/java/org/apache/poi/hssf/model/RecordStream.java new file mode 100755 index 000000000..03177c7c2 --- /dev/null +++ b/src/java/org/apache/poi/hssf/model/RecordStream.java @@ -0,0 +1,65 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hssf.model; + +import java.util.List; + +import org.apache.poi.hssf.record.Record; +/** + * Simplifies iteration over a sequence of Record objects. + * + * @author Josh Micich + */ +final class RecordStream { + + private final List _list; + private int _nextIndex; + private int _countRead; + + public RecordStream(List inputList, int startIndex) { + _list = inputList; + _nextIndex = startIndex; + _countRead = 0; + } + + public boolean hasNext() { + return _nextIndex < _list.size(); + } + + public Record getNext() { + if(_nextIndex >= _list.size()) { + throw new RuntimeException("Attempt to read past end of record stream"); + } + _countRead ++; + return (Record) _list.get(_nextIndex++); + } + + /** + * @return the {@link Class} of the next Record. null if this stream is exhausted. + */ + public Class peekNextClass() { + if(_nextIndex >= _list.size()) { + return null; + } + return _list.get(_nextIndex).getClass(); + } + + public int getCountRead() { + return _countRead; + } +} diff --git a/src/java/org/apache/poi/hssf/model/Workbook.java b/src/java/org/apache/poi/hssf/model/Workbook.java index 2ba50857c..8fa3010a4 100644 --- a/src/java/org/apache/poi/hssf/model/Workbook.java +++ b/src/java/org/apache/poi/hssf/model/Workbook.java @@ -79,10 +79,8 @@ public class Workbook implements Model */ protected SSTRecord sst = null; - /** - * Holds the Extern Sheet with references to bound sheets - */ - protected ExternSheetRecord externSheet= null; + + private LinkTable linkTable; // optionally occurs if there are references in the document. (4.10.3) /** * holds the "boundsheet" records (aka bundlesheet) so that they can have their @@ -92,8 +90,6 @@ public class Workbook implements Model protected ArrayList formats = new ArrayList(); - protected ArrayList names = new ArrayList(); - protected ArrayList hyperlinks = new ArrayList(); protected int numxfs = 0; // hold the number of extended format records @@ -134,6 +130,7 @@ public class Workbook implements Model new Integer(recs.size())); Workbook retval = new Workbook(); ArrayList records = new ArrayList(recs.size() / 3); + retval.records.setRecords(records); int k; for (k = 0; k < recs.size(); k++) { @@ -192,21 +189,16 @@ public class Workbook implements Model retval.records.setBackuppos( k ); break; case ExternSheetRecord.sid : - if (log.check( POILogger.DEBUG )) - log.log(DEBUG, "found extern sheet record at " + k); - retval.externSheet = ( ExternSheetRecord ) rec; - break; + throw new RuntimeException("Extern sheet is part of LinkTable"); case NameRecord.sid : - if (log.check( POILogger.DEBUG )) - log.log(DEBUG, "found name record at " + k); - retval.names.add(rec); - // retval.records.namepos = k; - break; + throw new RuntimeException("DEFINEDNAME is part of LinkTable"); case SupBookRecord.sid : if (log.check( POILogger.DEBUG )) log.log(DEBUG, "found SupBook record at " + k); + retval.linkTable = new LinkTable(recs, k, retval.records); // retval.records.supbookpos = k; - break; + k+=retval.linkTable.getRecordCount() - 1; + continue; case FormatRecord.sid : if (log.check( POILogger.DEBUG )) log.log(DEBUG, "found format record at " + k); @@ -262,8 +254,6 @@ public class Workbook implements Model break; } } - - retval.records.setRecords(records); if (retval.windowOne == null) { retval.windowOne = (WindowOneRecord) retval.createWindowOne(); @@ -283,6 +273,7 @@ public class Workbook implements Model log.log( DEBUG, "creating new workbook from scratch" ); Workbook retval = new Workbook(); ArrayList records = new ArrayList( 30 ); + retval.records.setRecords(records); ArrayList formats = new ArrayList( 8 ); records.add( retval.createBOF() ); @@ -339,8 +330,9 @@ public class Workbook implements Model records.add( retval.createStyle( k ) ); } records.add( retval.createUseSelFS() ); - for ( int k = 0; k < 1; k++ ) - { // now just do 1 + + int nBoundSheets = 1; // now just do 1 + for ( int k = 0; k < nBoundSheets; k++ ) { BoundSheetRecord bsr = (BoundSheetRecord) retval.createBoundSheet( k ); @@ -351,12 +343,14 @@ public class Workbook implements Model // retval.records.supbookpos = retval.records.bspos + 1; // retval.records.namepos = retval.records.supbookpos + 2; records.add( retval.createCountry() ); + for ( int k = 0; k < nBoundSheets; k++ ) { + retval.getOrCreateLinkTable().checkExternSheet(k); + } retval.sst = (SSTRecord) retval.createSST(); records.add( retval.sst ); records.add( retval.createExtendedSST() ); records.add( retval.createEOF() ); - retval.records.setRecords(records); if (log.check( POILogger.DEBUG )) log.log( DEBUG, "exit create new workbook from scratch" ); return retval; @@ -369,36 +363,20 @@ public class Workbook implements Model * @param sheetIndex Index to match * @return null if no builtin NameRecord matches */ - public NameRecord getSpecificBuiltinRecord(byte name, int sheetIndex) - { - Iterator iterator = names.iterator(); - while (iterator.hasNext()) { - NameRecord record = ( NameRecord ) iterator.next(); - - //print areas are one based - if (record.getBuiltInName() == name && record.getIndexToSheet() == sheetIndex) { - return record; - } - } - - return null; - - } + public NameRecord getSpecificBuiltinRecord(byte name, int sheetIndex) + { + return getOrCreateLinkTable().getSpecificBuiltinRecord(name, sheetIndex); + } /** * Removes the specified Builtin NameRecord that matches the name and index * @param name byte representation of the builtin to match * @param sheetIndex zero-based sheet reference */ - public void removeBuiltinRecord(byte name, int sheetIndex) { - //the name array is smaller so searching through it should be faster than - //using the findFirstXXXX methods - NameRecord record = getSpecificBuiltinRecord(name, sheetIndex); - if (record != null) { - names.remove(record); - } - - } + public void removeBuiltinRecord(byte name, int sheetIndex) { + linkTable.removeBuiltinRecord(name, sheetIndex); + // TODO - do we need "this.records.remove(...);" similar to that in this.removeName(int namenum) {}? + } public int getNumRecords() { return records.size(); @@ -614,6 +592,7 @@ public class Workbook implements Model records.add(records.getBspos()+1, bsr); records.setBspos( records.getBspos() + 1 ); boundsheets.add(bsr); + getOrCreateLinkTable().checkExternSheet(sheetnum); fixTabIdRecord(); } } @@ -1824,14 +1803,26 @@ public class Workbook implements Model protected Record createEOF() { return new EOFRecord(); } + + /** + * lazy initialization + * Note - creating the link table causes creation of 1 EXTERNALBOOK and 1 EXTERNALSHEET record + */ + private LinkTable getOrCreateLinkTable() { + if(linkTable == null) { + linkTable = new LinkTable((short) getNumSheets(), records); + } + return linkTable; + } public SheetReferences getSheetReferences() { SheetReferences refs = new SheetReferences(); - if (externSheet != null) { - for (int k = 0; k < externSheet.getNumOfREFStructures(); k++) { + if (linkTable != null) { + int numRefStructures = linkTable.getNumberOfREFStructures(); + for (short k = 0; k < numRefStructures; k++) { - String sheetName = findSheetNameFromExternSheet((short)k); + String sheetName = findSheetNameFromExternSheet(k); refs.addSheetReference(sheetName, k); } @@ -1846,7 +1837,8 @@ public class Workbook implements Model public String findSheetNameFromExternSheet(short num){ String result=""; - short indexToSheet = externSheet.getREFRecordAt(num).getIndexToFirstSupBook(); + short indexToSheet = linkTable.getIndexToSheet(num); + if (indexToSheet>-1) { //error check, bail out gracefully! result = getSheetName(indexToSheet); } @@ -1861,10 +1853,7 @@ public class Workbook implements Model */ public int getSheetIndexFromExternSheetIndex(int externSheetNumber) { - if (externSheetNumber >= externSheet.getNumOfREFStructures()) - return -1; - else - return externSheet.getREFRecordAt(externSheetNumber).getIndexToFirstSupBook(); + return linkTable.getSheetIndexFromExternSheetIndex(externSheetNumber); } /** returns the extern sheet number for specific sheet number , @@ -1873,58 +1862,17 @@ public class Workbook implements Model * @return index to extern sheet */ public short checkExternSheet(int sheetNumber){ - - int i = 0; - boolean flag = false; - short result = 0; - - if (externSheet == null) { - externSheet = createExternSheet(); - } - - //Trying to find reference to this sheet - while (i < externSheet.getNumOfREFStructures() && !flag){ - ExternSheetSubRecord record = externSheet.getREFRecordAt(i); - - if (record.getIndexToFirstSupBook() == sheetNumber && - record.getIndexToLastSupBook() == sheetNumber){ - flag = true; - result = (short) i; - } - - ++i; - } - - //We Havent found reference to this sheet - if (!flag) { - result = addSheetIndexToExternSheet((short) sheetNumber); - } - - return result; + return getOrCreateLinkTable().checkExternSheet(sheetNumber); } - private short addSheetIndexToExternSheet(short sheetNumber){ - short result; - - ExternSheetSubRecord record = new ExternSheetSubRecord(); - record.setIndexToFirstSupBook(sheetNumber); - record.setIndexToLastSupBook(sheetNumber); - externSheet.addREFRecord(record); - externSheet.setNumOfREFStructures((short)(externSheet.getNumOfREFStructures() + 1)); - result = (short)(externSheet.getNumOfREFStructures() - 1); - - return result; - } - - - /** gets the total number of names * @return number of names */ public int getNumNames(){ - int result = names.size(); - - return result; + if(linkTable == null) { + return 0; + } + return linkTable.getNumNames(); } /** gets the name record @@ -1932,28 +1880,14 @@ public class Workbook implements Model * @return name record */ public NameRecord getNameRecord(int index){ - NameRecord result = (NameRecord) names.get(index); - - return result; - + return linkTable.getNameRecord(index); } /** creates new name * @return new name record */ public NameRecord createName(){ - - NameRecord name = new NameRecord(); - - // Not the most efficient way but the other way was causing too many bugs - int idx = findFirstRecordLocBySid(ExternSheetRecord.sid); - if (idx == -1) idx = findFirstRecordLocBySid(SupBookRecord.sid); - if (idx == -1) idx = findFirstRecordLocBySid(CountryRecord.sid); - - records.add(idx+names.size()+1, name); - names.add(name); - - return name; + return addName(new NameRecord()); } @@ -1962,67 +1896,41 @@ public class Workbook implements Model */ public NameRecord addName(NameRecord name) { - // Not the most efficient way but the other way was causing too many bugs - int idx = findFirstRecordLocBySid(ExternSheetRecord.sid); - if (idx == -1) idx = findFirstRecordLocBySid(SupBookRecord.sid); - if (idx == -1) idx = findFirstRecordLocBySid(CountryRecord.sid); - records.add(idx+names.size()+1, name); - names.add(name); + + getOrCreateLinkTable().addName(name); return name; } - /**Generates a NameRecord to represent a built-in region - * @return a new NameRecord unless the index is invalid - */ - public NameRecord createBuiltInName(byte builtInName, int index) - { - if (index == -1 || index+1 > (int)Short.MAX_VALUE) - throw new IllegalArgumentException("Index is not valid ["+index+"]"); - - NameRecord name = new NameRecord(builtInName, (short)(index)); - - addName(name); - - return name; - } + /**Generates a NameRecord to represent a built-in region + * @return a new NameRecord unless the index is invalid + */ + public NameRecord createBuiltInName(byte builtInName, int index) + { + if (index == -1 || index+1 > Short.MAX_VALUE) + throw new IllegalArgumentException("Index is not valid ["+index+"]"); + + NameRecord name = new NameRecord(builtInName, (short)(index)); + + addName(name); + + return name; + } /** removes the name * @param namenum name index */ public void removeName(int namenum){ - if (names.size() > namenum) { + + if (linkTable.getNumNames() > namenum) { int idx = findFirstRecordLocBySid(NameRecord.sid); records.remove(idx + namenum); - names.remove(namenum); + linkTable.removeName(namenum); } } - /** creates a new extern sheet record - * @return the new extern sheet record - */ - protected ExternSheetRecord createExternSheet(){ - ExternSheetRecord externSheet = new ExternSheetRecord(); - - int idx = findFirstRecordLocBySid(CountryRecord.sid); - - records.add(idx+1, externSheet); -// records.add(records.supbookpos + 1 , rec); - - //We also adds the supBook for internal reference - SupBookRecord supbook = new SupBookRecord(); - - supbook.setNumberOfSheets((short)getNumSheets()); - //supbook.setFlag(); - - records.add(idx+1, supbook); -// records.add(records.supbookpos + 1 , supbook); - - return externSheet; - } - /** * Returns a format index that matches the passed in format. It does not tie into HSSFDataFormat. * @param format the format string @@ -2392,6 +2300,17 @@ public class Workbook implements Model } return this.fileShare; } + + /** + * is the workbook protected with a password (not encrypted)? + */ + public boolean isWriteProtected() { + if (this.fileShare == null) { + return false; + } + FileSharingRecord frec = getFileSharing(); + return (frec.getReadOnly() == 1); + } /** * protect a workbook with a password (not encypted, just sets writeprotect @@ -2419,5 +2338,14 @@ public class Workbook implements Model writeProtect = null; } + /** + * @param refIndex Index to REF entry in EXTERNSHEET record in the Link Table + * @param definedNameIndex zero-based to DEFINEDNAME or EXTERNALNAME record + * @return the string representation of the defined or external name + */ + public String resolveNameXText(int refIndex, int definedNameIndex) { + return linkTable.resolveNameXText(refIndex, definedNameIndex); + } } + diff --git a/src/java/org/apache/poi/hssf/record/CRNCountRecord.java b/src/java/org/apache/poi/hssf/record/CRNCountRecord.java new file mode 100755 index 000000000..4c9e4425c --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/CRNCountRecord.java @@ -0,0 +1,94 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hssf.record; + +import org.apache.poi.util.LittleEndian; +/** + * XCT – CRN Count

    + * + * REFERENCE: 5.114

    + * + * @author Josh Micich + */ +public final class CRNCountRecord extends Record { + public final static short sid = 0x59; + + private static final short BASE_RECORD_SIZE = 4; + + + private int field_1_number_crn_records; + private int field_2_sheet_table_index; + + public CRNCountRecord() { + throw new RuntimeException("incomplete code"); + } + + public CRNCountRecord(RecordInputStream in) { + super(in); + } + + protected void validateSid(short id) { + if (id != sid) { + throw new RecordFormatException("NOT An XCT RECORD"); + } + } + + public int getNumberOfCRNs() { + return field_1_number_crn_records; + } + + + protected void fillFields(RecordInputStream in) { + field_1_number_crn_records = in.readShort(); + if(field_1_number_crn_records < 0) { + // TODO - seems like the sign bit of this field might be used for some other purpose + // see example file for test case "TestBugs.test19599()" + field_1_number_crn_records = (short)-field_1_number_crn_records; + } + field_2_sheet_table_index = in.readShort(); + } + + + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append(getClass().getName()).append(" [XCT"); + sb.append(" nCRNs=").append(field_1_number_crn_records); + sb.append(" sheetIx=").append(field_2_sheet_table_index); + sb.append("]"); + return sb.toString(); + } + + public int serialize(int offset, byte [] data) { + LittleEndian.putShort(data, 0 + offset, sid); + LittleEndian.putShort(data, 2 + offset, BASE_RECORD_SIZE); + LittleEndian.putShort(data, 4 + offset, (short)field_1_number_crn_records); + LittleEndian.putShort(data, 6 + offset, (short)field_2_sheet_table_index); + return getRecordSize(); + } + + public int getRecordSize() { + return BASE_RECORD_SIZE + 4; + } + + /** + * return the non static version of the id for this record. + */ + public short getSid() { + return sid; + } +} diff --git a/src/java/org/apache/poi/hssf/record/CRNRecord.java b/src/java/org/apache/poi/hssf/record/CRNRecord.java new file mode 100755 index 000000000..73b9e42df --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/CRNRecord.java @@ -0,0 +1,99 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hssf.record; + +import org.apache.poi.hssf.record.constant.ConstantValueParser; +import org.apache.poi.util.LittleEndian; + +/** + * Title: CRN

    + * Description: This record stores the contents of an external cell or cell range

    + * REFERENCE: 5.23

    + * + * @author josh micich + */ +public final class CRNRecord extends Record { + public final static short sid = 0x5A; + + private int field_1_last_column_index; + private int field_2_first_column_index; + private int field_3_row_index; + private Object[] field_4_constant_values; + + public CRNRecord() { + throw new RuntimeException("incomplete code"); + } + + public CRNRecord(RecordInputStream in) { + super(in); + } + + protected void validateSid(short id) { + if (id != sid) { + throw new RecordFormatException("NOT An XCT RECORD"); + } + } + + public int getNumberOfCRNs() { + return field_1_last_column_index; + } + + + protected void fillFields(RecordInputStream in) { + field_1_last_column_index = in.readByte() & 0x00FF; + field_2_first_column_index = in.readByte() & 0x00FF; + field_3_row_index = in.readShort(); + int nValues = field_1_last_column_index - field_2_first_column_index + 1; + field_4_constant_values = ConstantValueParser.parse(in, nValues); + } + + + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append(getClass().getName()).append(" [CRN"); + sb.append(" rowIx=").append(field_3_row_index); + sb.append(" firstColIx=").append(field_2_first_column_index); + sb.append(" lastColIx=").append(field_1_last_column_index); + sb.append("]"); + return sb.toString(); + } + private int getDataSize() { + return 4 + ConstantValueParser.getEncodedSize(field_4_constant_values); + } + + public int serialize(int offset, byte [] data) { + int dataSize = getDataSize(); + LittleEndian.putShort(data, 0 + offset, sid); + LittleEndian.putShort(data, 2 + offset, (short) dataSize); + LittleEndian.putByte(data, 4 + offset, field_1_last_column_index); + LittleEndian.putByte(data, 5 + offset, field_2_first_column_index); + LittleEndian.putShort(data, 6 + offset, (short) field_3_row_index); + return getRecordSize(); + } + + public int getRecordSize() { + return getDataSize() + 4; + } + + /** + * return the non static version of the id for this record. + */ + public short getSid() { + return sid; + } +} diff --git a/src/java/org/apache/poi/hssf/record/DVALRecord.java b/src/java/org/apache/poi/hssf/record/DVALRecord.java index 2846f5066..011c0a435 100644 --- a/src/java/org/apache/poi/hssf/record/DVALRecord.java +++ b/src/java/org/apache/poi/hssf/record/DVALRecord.java @@ -1,4 +1,3 @@ - /* ==================================================================== Copyright 2002-2004 Apache Software Foundation @@ -20,13 +19,11 @@ package org.apache.poi.hssf.record; import org.apache.poi.util.LittleEndian; /** - * Title: DVAL Record

    + * Title: DATAVALIDATIONS Record

    * Description: used in data validation ; - * This record is the list header of all data validation records in the current sheet. + * This record is the list header of all data validation records (0x01BE) in the current sheet. * @author Dragos Buleandra (dragos.buleandra@trade2b.ro) - * @version 2.0-pre */ - public class DVALRecord extends Record { public final static short sid = 0x01B2; @@ -41,13 +38,14 @@ public class DVALRecord extends Record /** Object ID of the drop down arrow object for list boxes ; * in our case this will be always FFFF , until * MSODrawingGroup and MSODrawing records are implemented */ - private int field_cbo_id = 0xFFFFFFFF; + private int field_cbo_id; /** Number of following DV Records */ - private int field_5_dv_no = 0x00000000; + private int field_5_dv_no; - public DVALRecord() - { + public DVALRecord() { + field_cbo_id = 0xFFFFFFFF; + field_5_dv_no = 0x00000000; } /** diff --git a/src/java/org/apache/poi/hssf/record/ExternalNameRecord.java b/src/java/org/apache/poi/hssf/record/ExternalNameRecord.java new file mode 100755 index 000000000..771603c85 --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/ExternalNameRecord.java @@ -0,0 +1,179 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hssf.record; + +import java.util.List; +import java.util.Stack; + +import org.apache.poi.hssf.record.formula.Ptg; +import org.apache.poi.util.LittleEndian; +import org.apache.poi.util.StringUtil; + +/** + * EXTERNALNAME

    + * + * @author josh micich + */ +public final class ExternalNameRecord extends Record { + + public final static short sid = 0x23; // as per BIFF8. (some old versions used 0x223) + + + private static final int OPT_BUILTIN_NAME = 0x0001; + private static final int OPT_AUTOMATIC_LINK = 0x0002; + private static final int OPT_PICTURE_LINK = 0x0004; + private static final int OPT_STD_DOCUMENT_NAME = 0x0008; + private static final int OPT_OLE_LINK = 0x0010; +// private static final int OPT_CLIP_FORMAT_MASK = 0x7FE0; + private static final int OPT_ICONIFIED_PICTURE_LINK = 0x8000; + + + private short field_1_option_flag; + private short field_2_index; + private short field_3_not_used; + private String field_4_name; + private Stack field_5_name_definition; + + + public ExternalNameRecord(RecordInputStream in) { + super(in); + } + + /** + * Convenience Function to determine if the name is a built-in name + */ + public boolean isBuiltInName() { + return (field_1_option_flag & OPT_BUILTIN_NAME) != 0; + } + /** + * For OLE and DDE, links can be either 'automatic' or 'manual' + */ + public boolean isAutomaticLink() { + return (field_1_option_flag & OPT_AUTOMATIC_LINK) != 0; + } + /** + * only for OLE and DDE + */ + public boolean isPicureLink() { + return (field_1_option_flag & OPT_PICTURE_LINK) != 0; + } + /** + * DDE links only. If true, this denotes the 'StdDocumentName' + */ + public boolean isStdDocumentNameIdentifier() { + return (field_1_option_flag & OPT_STD_DOCUMENT_NAME) != 0; + } + public boolean isOLELink() { + return (field_1_option_flag & OPT_OLE_LINK) != 0; + } + public boolean isIconifiedPictureLink() { + return (field_1_option_flag & OPT_ICONIFIED_PICTURE_LINK) != 0; + } + /** + * @return the standard String representation of this name + */ + public String getText() { + return field_4_name; + } + + + /** + * 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 valid ExternalName RECORD"); + } + } + + private int getDataSize(){ + return 2 + 2 + field_4_name.length() + 2 + getNameDefinitionSize(); + } + + /** + * called by the class that is responsible for writing this sucker. + * Subclasses should implement this so that their data is passed back in a + * byte array. + * + * @param offset to begin writing at + * @param data byte array containing instance data + * @return number of bytes written + */ + public int serialize( int offset, byte[] data ) { + // TODO - junit tests + int dataSize = getDataSize(); + + LittleEndian.putShort( data, 0 + offset, sid ); + LittleEndian.putShort( data, 2 + offset, (short) dataSize ); + LittleEndian.putShort( data, 4 + offset, field_1_option_flag ); + LittleEndian.putShort( data, 6 + offset, field_2_index ); + LittleEndian.putShort( data, 8 + offset, field_3_not_used ); + short nameLen = (short) field_4_name.length(); + LittleEndian.putShort( data, 10 + offset, nameLen ); + StringUtil.putCompressedUnicode( field_4_name, data, 10 + offset ); + short defLen = (short) getNameDefinitionSize(); + LittleEndian.putShort( data, 12 + nameLen + offset, defLen ); + Ptg.serializePtgStack(field_5_name_definition, data, 12 + nameLen + offset ); + return dataSize + 4; + } + + private int getNameDefinitionSize() { + int result = 0; + List list = field_5_name_definition; + + for (int k = 0; k < list.size(); k++) + { + Ptg ptg = ( Ptg ) list.get(k); + + result += ptg.getSize(); + } + return result; + } + + + public int getRecordSize(){ + return 6 + 2 + field_4_name.length() + 2 + getNameDefinitionSize(); + } + + + protected void fillFields(RecordInputStream in) { + field_1_option_flag = in.readShort(); + field_2_index = in.readShort(); + field_3_not_used = in.readShort(); + short nameLength = in.readShort(); + field_4_name = in.readCompressedUnicode(nameLength); + short formulaLen = in.readShort(); + field_5_name_definition = Ptg.createParsedExpressionTokens(formulaLen, in); + } + + public short getSid() { + return sid; + } + + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append(getClass().getName()).append(" [EXTERNALNAME "); + sb.append(" ").append(field_4_name); + sb.append(" ix=").append(field_2_index); + sb.append("]"); + return sb.toString(); + } +} diff --git a/src/java/org/apache/poi/hssf/record/FileSharingRecord.java b/src/java/org/apache/poi/hssf/record/FileSharingRecord.java index 17f8cda64..2f56eb092 100644 --- a/src/java/org/apache/poi/hssf/record/FileSharingRecord.java +++ b/src/java/org/apache/poi/hssf/record/FileSharingRecord.java @@ -19,6 +19,8 @@ package org.apache.poi.hssf.record; import org.apache.poi.util.LittleEndian; +import org.apache.poi.util.POILogFactory; +import org.apache.poi.util.POILogger; import org.apache.poi.util.StringUtil; /** @@ -29,6 +31,8 @@ import org.apache.poi.util.StringUtil; */ public class FileSharingRecord extends Record { + private static POILogger logger = POILogFactory.getLogger(FileSharingRecord.class); + public final static short sid = 0x5b; private short field_1_readonly; private short field_2_password; @@ -58,8 +62,23 @@ public class FileSharingRecord extends Record { field_1_readonly = in.readShort(); field_2_password = in.readShort(); field_3_username_length = in.readByte(); + + // Is this really correct? The latest docs + // seem to hint there's nothing between the + // username length and the username string field_4_unknown = in.readShort(); - field_5_username = in.readCompressedUnicode(field_3_username_length); + + // Ensure we don't try to read more data than + // there actually is + if(field_3_username_length > in.remaining()) { + logger.log(POILogger.WARN, "FileSharingRecord defined a username of length " + field_3_username_length + ", but only " + in.remaining() + " bytes were left, truncating"); + field_3_username_length = (byte)in.remaining(); + } + if(field_3_username_length > 0) { + field_5_username = in.readCompressedUnicode(field_3_username_length); + } else { + field_5_username = ""; + } } //this is the world's lamest "security". thanks to Wouter van Vugt for making me diff --git a/src/java/org/apache/poi/hssf/record/NameRecord.java b/src/java/org/apache/poi/hssf/record/NameRecord.java index 3b1c413d5..a06bc8aed 100644 --- a/src/java/org/apache/poi/hssf/record/NameRecord.java +++ b/src/java/org/apache/poi/hssf/record/NameRecord.java @@ -726,7 +726,7 @@ public class NameRecord extends Record { for(int i=0; i 0) { + field_3_string_options = in.readByte(); + + byte[] string = in.readRemainder(); + if (fHighByte.isSet(field_3_string_options)) { + field_4_name= StringUtil.getFromUnicodeBE(string, 0, field_2_name_length); + } else { + field_4_name=StringUtil.getFromCompressedUnicode(string, 0, field_2_name_length); + } } } diff --git a/src/java/org/apache/poi/hssf/record/SupBookRecord.java b/src/java/org/apache/poi/hssf/record/SupBookRecord.java index 91a16f079..6755aa6f8 100644 --- a/src/java/org/apache/poi/hssf/record/SupBookRecord.java +++ b/src/java/org/apache/poi/hssf/record/SupBookRecord.java @@ -1,4 +1,3 @@ - /* ==================================================================== Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with @@ -15,72 +14,159 @@ See the License for the specific language governing permissions and limitations under the License. ==================================================================== */ - package org.apache.poi.hssf.record; +import org.apache.poi.hssf.record.UnicodeString.UnicodeRecordStats; import org.apache.poi.util.LittleEndian; /** - * Title: Sup Book

    - * Description: A Extrenal Workbook Deciption (Sup Book) + * Title: Sup Book (EXTERNALBOOK)

    + * Description: A External Workbook Description (Suplemental Book) * Its only a dummy record for making new ExternSheet Record

    - * REFERENCE:

    + * REFERENCE: 5.38

    * @author Libin Roman (Vista Portal LDT. Developer) * @author Andrew C. Oliver (acoliver@apache.org) * */ -public class SupBookRecord extends Record -{ +public final class SupBookRecord extends Record { + public final static short sid = 0x1AE; + + private static final short SMALL_RECORD_SIZE = 4; + private static final short TAG_INTERNAL_REFERENCES = 0x0401; + private static final short TAG_ADD_IN_FUNCTIONS = 0x3A01; + private short field_1_number_of_sheets; - private short field_2_flag; + private UnicodeString field_2_encoded_url; + private UnicodeString[] field_3_sheet_names; + private boolean _isAddInFunctions; - - public SupBookRecord() - { - setFlag((short)0x401); + + public static SupBookRecord createInternalReferences(short numberOfSheets) { + return new SupBookRecord(false, numberOfSheets); + } + public static SupBookRecord createAddInFunctions() { + return new SupBookRecord(true, (short)0); + } + public static SupBookRecord createExternalReferences(UnicodeString url, UnicodeString[] sheetNames) { + return new SupBookRecord(url, sheetNames); + } + private SupBookRecord(boolean isAddInFuncs, short numberOfSheets) { + // else not 'External References' + field_1_number_of_sheets = numberOfSheets; + field_2_encoded_url = null; + field_3_sheet_names = null; + _isAddInFunctions = isAddInFuncs; + } + public SupBookRecord(UnicodeString url, UnicodeString[] sheetNames) { + field_1_number_of_sheets = (short) sheetNames.length; + field_2_encoded_url = url; + field_3_sheet_names = sheetNames; + _isAddInFunctions = false; } /** * Constructs a Extern Sheet record and sets its fields appropriately. * - * @param in the RecordInputstream to read the record from + * @param id id must be 0x16 or an exception will be throw upon validation + * @param size the size of the data area of the record + * @param data data of the record (should not contain sid/len) */ - public SupBookRecord(RecordInputStream in) - { + public SupBookRecord(RecordInputStream in) { super(in); } - protected void validateSid(short id) - { - if (id != sid) - { - throw new RecordFormatException("NOT An Supbook RECORD"); + protected void validateSid(short id) { + if (id != sid) { + throw new RecordFormatException("NOT An ExternSheet RECORD"); } } - /** - * @param in the RecordInputstream to read the record from - */ - protected void fillFields(RecordInputStream in) - { - //For now We use it only for one case - //When we need to add an named range when no named ranges was - //before it - field_1_number_of_sheets = in.readShort(); - field_2_flag = in.readShort(); + public boolean isExternalReferences() { + return field_3_sheet_names != null; } + public boolean isInternalReferences() { + return field_3_sheet_names == null && !_isAddInFunctions; + } + public boolean isAddInFunctions() { + return field_3_sheet_names == null && _isAddInFunctions; + } + /** + * called by the constructor, should set class level fields. Should throw + * runtime exception for bad/incomplete data. + * + * @param data raw data + * @param size size of data + * @param offset of the record's data (provided a big array of the file) + */ + protected void fillFields(RecordInputStream in) { + field_1_number_of_sheets = in.readShort(); + + if(in.getLength() > SMALL_RECORD_SIZE) { + // 5.38.1 External References + _isAddInFunctions = false; + field_2_encoded_url = in.readUnicodeString(); + UnicodeString[] sheetNames = new UnicodeString[field_1_number_of_sheets]; + for (int i = 0; i < sheetNames.length; i++) { + sheetNames[i] = in.readUnicodeString(); + } + field_3_sheet_names = sheetNames; + return; + } + // else not 'External References' + field_2_encoded_url = null; + field_3_sheet_names = null; + + short nextShort = in.readShort(); + if(nextShort == TAG_INTERNAL_REFERENCES) { + // 5.38.2 'Internal References' + _isAddInFunctions = false; + } else if(nextShort == TAG_ADD_IN_FUNCTIONS) { + // 5.38.3 'Add-In Functions' + _isAddInFunctions = true; + if(field_1_number_of_sheets != 1) { + throw new RuntimeException("Expected 0x0001 for number of sheets field in 'Add-In Functions' but got (" + + field_1_number_of_sheets + ")"); + } + } else { + throw new RuntimeException("invalid EXTERNALBOOK code (" + + Integer.toHexString(nextShort) + ")"); + } + } - public String toString() - { - StringBuffer buffer = new StringBuffer(); - buffer.append("[SUPBOOK]\n"); - buffer.append("numberosheets = ").append(getNumberOfSheets()).append('\n'); - buffer.append("flag = ").append(getFlag()).append('\n'); - buffer.append("[/SUPBOOK]\n"); - return buffer.toString(); + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append(getClass().getName()).append(" [SUPBOOK "); + + if(isExternalReferences()) { + sb.append("External References"); + sb.append(" nSheets=").append(field_1_number_of_sheets); + sb.append(" url=").append(field_2_encoded_url); + } else if(_isAddInFunctions) { + sb.append("Add-In Functions"); + } else { + sb.append("Internal References "); + sb.append(" nSheets= ").append(field_1_number_of_sheets); + } + return sb.toString(); + } + private int getDataSize() { + if(!isExternalReferences()) { + return SMALL_RECORD_SIZE; + } + int sum = 2; // u16 number of sheets + UnicodeRecordStats urs = new UnicodeRecordStats(); + field_2_encoded_url.getRecordSize(urs); + sum += urs.recordSize; + + for(int i=0; i=0;i--) { + // TODO - there is no junit test case to justify this reversed loop + // perhaps it could just run in the normal direction? SharedFormulaRecord shrd = (SharedFormulaRecord)sharedFormulas.get(i); if (shrd.isFormulaInShared(formula)) { shrd.convertSharedFormulaRecord(formula); @@ -164,9 +170,7 @@ public class ValueRecordsAggregate } } if (!found) { - //Sometimes the shared formula flag "seems" to be errornously set, - //cant really do much about that. - //throw new RecordFormatException("Could not find appropriate shared formula"); + handleMissingSharedFormulaRecord(formula); } } @@ -185,6 +189,24 @@ public class ValueRecordsAggregate return k; } + /** + * Sometimes the shared formula flag "seems" to be erroneously set, in which case there is no + * call to SharedFormulaRecord.convertSharedFormulaRecord and hence the + * parsedExpression field of this FormulaRecord will not get updated.
    + * As it turns out, this is not a problem, because in these circumstances, the existing value + * for parsedExpression is perfectly OK.

    + * + * This method may also be used for setting breakpoints to help diagnose issues regarding the + * abnormally-set 'shared formula' flags. + * (see TestValueRecordsAggregate.testSpuriousSharedFormulaFlag()).

    + * + * The method currently does nothing but do not delete it without finding a nice home for this + * comment. + */ + private static void handleMissingSharedFormulaRecord(FormulaRecord formula) { + // could log an info message here since this is a fairly unusual occurrence. + } + /** * called by the class that is responsible for writing this sucker. * Subclasses should implement this so that their data is passed back in a @@ -300,7 +322,7 @@ public class ValueRecordsAggregate return rec; } - public class MyIterator implements Iterator { + private final class MyIterator implements Iterator { short nextColumn=-1; int nextRow,lastRow; diff --git a/src/java/org/apache/poi/hssf/record/constant/ConstantValueParser.java b/src/java/org/apache/poi/hssf/record/constant/ConstantValueParser.java new file mode 100755 index 000000000..6ec831e4a --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/constant/ConstantValueParser.java @@ -0,0 +1,111 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hssf.record.constant; + +import org.apache.poi.hssf.record.RecordInputStream; +import org.apache.poi.hssf.record.UnicodeString; +import org.apache.poi.hssf.record.UnicodeString.UnicodeRecordStats; + +/** + * To support Constant Values (2.5.7) as required by the CRN record. + * This class should probably also be used for two dimensional arrays which are encoded by + * EXTERNALNAME (5.39) records and Array tokens.

    + * TODO - code in ArrayPtg should be merged with this code. It currently supports only 2 of the constant types + * + * @author Josh Micich + */ +public final class ConstantValueParser { + // note - value 3 seems to be unused + private static final int TYPE_EMPTY = 0; + private static final int TYPE_NUMBER = 1; + private static final int TYPE_STRING = 2; + private static final int TYPE_BOOLEAN = 4; + + private static final int TRUE_ENCODING = 1; + private static final int FALSE_ENCODING = 0; + + // TODO - is this the best way to represent 'EMPTY'? + private static final Object EMPTY_REPRESENTATION = null; + + private ConstantValueParser() { + // no instances of this class + } + + public static Object[] parse(RecordInputStream in, int nValues) { + Object[] result = new Object[nValues]; + for (int i = 0; i < result.length; i++) { + result[i] = readAConstantValue(in); + } + return result; + } + + private static Object readAConstantValue(RecordInputStream in) { + byte grbit = in.readByte(); + switch(grbit) { + case TYPE_EMPTY: + in.readLong(); // 8 byte 'not used' field + return EMPTY_REPRESENTATION; + case TYPE_NUMBER: + return new Double(in.readDouble()); + case TYPE_STRING: + return in.readUnicodeString(); + case TYPE_BOOLEAN: + return readBoolean(in); + } + return null; + } + + private static Object readBoolean(RecordInputStream in) { + byte val = in.readByte(); + in.readLong(); // 8 byte 'not used' field + switch(val) { + case FALSE_ENCODING: + return Boolean.FALSE; + case TRUE_ENCODING: + return Boolean.TRUE; + } + // Don't tolerate unusual boolean encoded values (unless it becomes evident that they occur) + throw new RuntimeException("unexpected boolean encoding (" + val + ")"); + } + + public static int getEncodedSize(Object[] values) { + // start with one byte 'type' code for each value + int result = values.length * 1; + for (int i = 0; i < values.length; i++) { + result += getEncodedSize(values[i]); + } + return 0; + } + + /** + * @return encoded size without the 'type' code byte + */ + private static int getEncodedSize(Object object) { + if(object == EMPTY_REPRESENTATION) { + return 8; + } + Class cls = object.getClass(); + if(cls == Boolean.class || cls == Double.class) { + return 8; + } + UnicodeString strVal = (UnicodeString)object; + UnicodeRecordStats urs = new UnicodeRecordStats(); + strVal.getRecordSize(urs); + return urs.recordSize; + } +} diff --git a/src/java/org/apache/poi/hssf/record/formula/AbstractFunctionPtg.java b/src/java/org/apache/poi/hssf/record/formula/AbstractFunctionPtg.java index 555c50294..c7c57280d 100644 --- a/src/java/org/apache/poi/hssf/record/formula/AbstractFunctionPtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/AbstractFunctionPtg.java @@ -29,10 +29,13 @@ import org.apache.poi.hssf.model.Workbook; * @author Andrew C. Oliver (acoliver at apache dot org) */ public abstract class AbstractFunctionPtg extends OperationPtg { - //constant used allow a ptgAttr to be mapped properly for its functionPtg - public static final String ATTR_NAME = "specialflag"; - - public static final short INDEX_EXTERNAL = 255; + + /** + * The name of the IF function (i.e. "IF"). Extracted as a constant for clarity. + */ + public static final String FUNCTION_NAME_IF = "IF"; + /** All external functions have function index 255 */ + private static final short FUNCTION_INDEX_EXTERNAL = 255; private static BinaryTree map = produceHash(); protected static Object[][] functionData = produceFunctionData(); @@ -66,6 +69,13 @@ public abstract class AbstractFunctionPtg extends OperationPtg { public String getName() { return lookupName(field_2_fnc_index); } + /** + * external functions get some special processing + * @return true if this is an external function + */ + public boolean isExternalFunction() { + return field_2_fnc_index == FUNCTION_INDEX_EXTERNAL; + } public String toFormulaString(Workbook book) { return getName(); @@ -73,39 +83,57 @@ public abstract class AbstractFunctionPtg extends OperationPtg { public String toFormulaString(String[] operands) { StringBuffer buf = new StringBuffer(); - - if (field_2_fnc_index != 1) { - buf.append(getName()); - buf.append('('); - } - if (operands.length >0) { - for (int i=0;ifirstArgIx) { + buf.append(','); + } + buf.append(operands[i]); + } + buf.append(")"); + } public abstract void writeBytes(byte[] array, int offset); public abstract int getSize(); - - + /** + * Used to detect whether a function name found in a formula is one of the standard excel functions + *

    + * The name matching is case insensitive. + * @return true if the name specifies a standard worksheet function, + * false if the name should be assumed to be an external function. + */ + public static final boolean isInternalFunctionName(String name) { + return map.containsValue(name.toUpperCase()); + } protected String lookupName(short index) { return ((String)map.get(new Integer(index))); } - protected short lookupIndex(String name) { - Integer index = (Integer) map.getKeyForValue(name); + /** + * Resolves internal function names into function indexes. + *

    + * The name matching is case insensitive. + * @return the standard worksheet function index if found, otherwise FUNCTION_INDEX_EXTERNAL + */ + protected static short lookupIndex(String name) { + Integer index = (Integer) map.getKeyForValue(name.toUpperCase()); if (index != null) return index.shortValue(); - return INDEX_EXTERNAL; + return FUNCTION_INDEX_EXTERNAL; } /** @@ -115,7 +143,7 @@ public abstract class AbstractFunctionPtg extends OperationPtg { BinaryTree dmap = new BinaryTree(); dmap.put(new Integer(0),"COUNT"); - dmap.put(new Integer(1),"specialflag"); + dmap.put(new Integer(1),FUNCTION_NAME_IF); dmap.put(new Integer(2),"ISNA"); dmap.put(new Integer(3),"ISERROR"); dmap.put(new Integer(4),"SUM"); @@ -354,7 +382,7 @@ public abstract class AbstractFunctionPtg extends OperationPtg { dmap.put(new Integer(252),"FREQUENCY"); dmap.put(new Integer(253),"ADDTOOLBAR"); dmap.put(new Integer(254),"DELETETOOLBAR"); - dmap.put(new Integer(255),"externalflag"); + dmap.put(new Integer(FUNCTION_INDEX_EXTERNAL),"externalflag"); dmap.put(new Integer(256),"RESETTOOLBAR"); dmap.put(new Integer(257),"EVALUATE"); dmap.put(new Integer(258),"GETTOOLBAR"); diff --git a/src/java/org/apache/poi/hssf/record/formula/Area3DPtg.java b/src/java/org/apache/poi/hssf/record/formula/Area3DPtg.java index ac260ffa4..33278e25e 100644 --- a/src/java/org/apache/poi/hssf/record/formula/Area3DPtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/Area3DPtg.java @@ -17,15 +17,13 @@ package org.apache.poi.hssf.record.formula; -import org.apache.poi.util.LittleEndian; -import org.apache.poi.hssf.util.AreaReference; -import org.apache.poi.hssf.util.CellReference; -import org.apache.poi.hssf.util.SheetReferences; - import org.apache.poi.hssf.model.Workbook; import org.apache.poi.hssf.record.RecordInputStream; +import org.apache.poi.hssf.util.AreaReference; +import org.apache.poi.hssf.util.CellReference; import org.apache.poi.util.BitField; import org.apache.poi.util.BitFieldFactory; +import org.apache.poi.util.LittleEndian; /** @@ -38,15 +36,15 @@ import org.apache.poi.util.BitFieldFactory; * @version 1.0-pre */ -public class Area3DPtg extends Ptg +public class Area3DPtg extends Ptg implements AreaI { public final static byte sid = 0x3b; private final static int SIZE = 11; // 10 + 1 for Ptg private short field_1_index_extern_sheet; - private short field_2_first_row; - private short field_3_last_row; - private short field_4_first_column; - private short field_5_last_column; + private int field_2_first_row; + private int field_3_last_row; + private int field_4_first_column; + private int field_5_last_column; private BitField rowRelative = BitFieldFactory.getInstance( 0x8000 ); private BitField colRelative = BitFieldFactory.getInstance( 0x4000 ); @@ -66,10 +64,24 @@ public class Area3DPtg extends Ptg public Area3DPtg(RecordInputStream in) { field_1_index_extern_sheet = in.readShort(); - field_2_first_row = in.readShort(); - field_3_last_row = in.readShort(); - field_4_first_column = in.readShort(); - field_5_last_column = in.readShort(); + field_2_first_row = in.readUShort(); + field_3_last_row = in.readUShort(); + field_4_first_column = in.readUShort(); + field_5_last_column = in.readUShort(); + } + + public Area3DPtg(short firstRow, short lastRow, short firstColumn, short lastColumn, + boolean firstRowRelative, boolean lastRowRelative, boolean firstColRelative, boolean lastColRelative, + short externalSheetIndex) { + setFirstRow(firstRow); + setLastRow(lastRow); + setFirstColumn(firstColumn); + setLastColumn(lastColumn); + setFirstRowRelative(firstRowRelative); + setLastRowRelative(lastRowRelative); + setFirstColRelative(firstColRelative); + setLastColRelative(lastColRelative); + setExternSheetIndex(externalSheetIndex); } public String toString() @@ -87,7 +99,7 @@ public class Area3DPtg extends Ptg buffer.append( "lastColRowRel = " + isLastRowRelative() ).append( "\n" ); buffer.append( "firstColRel = " + isFirstColRelative() ).append( "\n" ); - buffer.append( "lastColRel = " + isLastColRelative() ).append( "\n" ); + buffer.append( "lastColRel = " + isLastColRelative() ).append( "\n" ); return buffer.toString(); } @@ -95,10 +107,10 @@ public class Area3DPtg extends Ptg { array[0 + offset] = (byte) ( sid + ptgClass ); LittleEndian.putShort( array, 1 + offset, getExternSheetIndex() ); - LittleEndian.putShort( array, 3 + offset, getFirstRow() ); - LittleEndian.putShort( array, 5 + offset, getLastRow() ); - LittleEndian.putShort( array, 7 + offset, getFirstColumnRaw() ); - LittleEndian.putShort( array, 9 + offset, getLastColumnRaw() ); + LittleEndian.putShort( array, 3 + offset, (short)getFirstRow() ); + LittleEndian.putShort( array, 5 + offset, (short)getLastRow() ); + LittleEndian.putShort( array, 7 + offset, (short)getFirstColumnRaw() ); + LittleEndian.putShort( array, 9 + offset, (short)getLastColumnRaw() ); } public int getSize() @@ -116,32 +128,32 @@ public class Area3DPtg extends Ptg field_1_index_extern_sheet = index; } - public short getFirstRow() + public int getFirstRow() { return field_2_first_row; } - public void setFirstRow( short row ) + public void setFirstRow( int row ) { field_2_first_row = row; } - public short getLastRow() + public int getLastRow() { return field_3_last_row; } - public void setLastRow( short row ) + public void setLastRow( int row ) { field_3_last_row = row; } - public short getFirstColumn() + public int getFirstColumn() { - return (short) ( field_4_first_column & 0xFF ); + return field_4_first_column & 0xFF; } - public short getFirstColumnRaw() + public int getFirstColumnRaw() { return field_4_first_column; } @@ -167,12 +179,12 @@ public class Area3DPtg extends Ptg field_4_first_column = column; } - public short getLastColumn() + public int getLastColumn() { - return (short) ( field_5_last_column & 0xFF ); + return field_5_last_column & 0xFF; } - public short getLastColumnRaw() + public int getLastColumnRaw() { return field_5_last_column; } @@ -204,7 +216,7 @@ public class Area3DPtg extends Ptg */ public void setFirstRowRelative( boolean rel ) { - field_4_first_column = rowRelative.setShortBoolean( field_4_first_column, rel ); + field_4_first_column = rowRelative.setBoolean( field_4_first_column, rel ); } /** @@ -212,7 +224,7 @@ public class Area3DPtg extends Ptg */ public void setFirstColRelative( boolean rel ) { - field_4_first_column = colRelative.setShortBoolean( field_4_first_column, rel ); + field_4_first_column = colRelative.setBoolean( field_4_first_column, rel ); } /** @@ -221,7 +233,7 @@ public class Area3DPtg extends Ptg */ public void setLastRowRelative( boolean rel ) { - field_5_last_column = rowRelative.setShortBoolean( field_5_last_column, rel ); + field_5_last_column = rowRelative.setBoolean( field_5_last_column, rel ); } /** @@ -229,7 +241,7 @@ public class Area3DPtg extends Ptg */ public void setLastColRelative( boolean rel ) { - field_5_last_column = colRelative.setShortBoolean( field_5_last_column, rel ); + field_5_last_column = colRelative.setBoolean( field_5_last_column, rel ); } @@ -243,39 +255,38 @@ public class Area3DPtg extends Ptg public void setArea( String ref ) { AreaReference ar = new AreaReference( ref ); - CellReference[] crs = ar.getCells(); - CellReference firstCell = crs[0]; - CellReference lastCell = firstCell; - if(crs.length > 1) { - lastCell = crs[1]; - } + CellReference frstCell = ar.getFirstCell(); + CellReference lastCell = ar.getLastCell(); - setFirstRow( (short) firstCell.getRow() ); - setFirstColumn( (short) firstCell.getCol() ); - setLastRow( (short) lastCell.getRow() ); - setLastColumn( (short) lastCell.getCol() ); - setFirstColRelative( !firstCell.isColAbsolute() ); + setFirstRow( (short) frstCell.getRow() ); + setFirstColumn( frstCell.getCol() ); + setLastRow( (short) lastCell.getRow() ); + setLastColumn( lastCell.getCol() ); + setFirstColRelative( !frstCell.isColAbsolute() ); setLastColRelative( !lastCell.isColAbsolute() ); - setFirstRowRelative( !firstCell.isRowAbsolute() ); + setFirstRowRelative( !frstCell.isRowAbsolute() ); setLastRowRelative( !lastCell.isRowAbsolute() ); } - /** - * @return text representation of this area reference that can be used in text - * formulas. The sheet name will get properly delimited if required. - */ + /** + * @return text representation of this area reference that can be used in text + * formulas. The sheet name will get properly delimited if required. + */ public String toFormulaString(Workbook book) { + // First do the sheet name StringBuffer retval = new StringBuffer(); String sheetName = Ref3DPtg.getSheetName(book, field_1_index_extern_sheet); if(sheetName != null) { SheetNameFormatter.appendFormat(retval, sheetName); retval.append( '!' ); } - retval.append( ( new CellReference( getFirstRow(), getFirstColumn(), !isFirstRowRelative(), !isFirstColRelative() ) ).toString() ); - retval.append( ':' ); - retval.append( ( new CellReference( getLastRow(), getLastColumn(), !isLastRowRelative(), !isLastColRelative() ) ).toString() ); + + // Now the normal area bit + retval.append( AreaPtg.toFormulaString(this, book) ); + + // All done return retval.toString(); } @@ -292,7 +303,7 @@ public class Area3DPtg extends Ptg ptg.field_3_last_row = field_3_last_row; ptg.field_4_first_column = field_4_first_column; ptg.field_5_last_column = field_5_last_column; - ptg.setClass(ptgClass); + ptg.setClass(ptgClass); return ptg; } diff --git a/src/java/org/apache/poi/hssf/record/formula/AreaAPtg.java b/src/java/org/apache/poi/hssf/record/formula/AreaAPtg.java index 57628f19b..515d07dd4 100644 --- a/src/java/org/apache/poi/hssf/record/formula/AreaAPtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/AreaAPtg.java @@ -36,16 +36,14 @@ import org.apache.poi.hssf.model.Workbook; * @author Jason Height (jheight at chariot dot net dot au) */ -public class AreaAPtg - extends AreaPtg -{ +public final class AreaAPtg extends AreaPtg { public final static short sid = 0x65; protected AreaAPtg() { //Required for clone methods } - public AreaAPtg(short firstRow, short lastRow, short firstColumn, short lastColumn, boolean firstRowRelative, boolean lastRowRelative, boolean firstColRelative, boolean lastColRelative) { + public AreaAPtg(int firstRow, int lastRow, int firstColumn, int lastColumn, boolean firstRowRelative, boolean lastRowRelative, boolean firstColRelative, boolean lastColRelative) { super(firstRow, lastRow, firstColumn, lastColumn, firstRowRelative, lastRowRelative, firstColRelative, lastColRelative); } diff --git a/src/java/org/apache/poi/hssf/record/formula/AreaI.java b/src/java/org/apache/poi/hssf/record/formula/AreaI.java new file mode 100644 index 000000000..5a0a21e86 --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/formula/AreaI.java @@ -0,0 +1,60 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ +package org.apache.poi.hssf.record.formula; + +/** + * Common interface for AreaPtg and Area3DPtg, and their + * child classes. + */ +public interface AreaI { + /** + * @return the first row in the area + */ + public int getFirstRow(); + + /** + * @return last row in the range (x2 in x1,y1-x2,y2) + */ + public int getLastRow(); + + /** + * @return the first column number in the area. + */ + public int getFirstColumn(); + + /** + * @return lastcolumn in the area + */ + public int getLastColumn(); + + /** + * @return isrelative first column to relative or not + */ + public boolean isFirstColRelative(); + /** + * @return lastcol relative or not + */ + public boolean isLastColRelative(); + /** + * @return whether or not the first row is a relative reference or not. + */ + public boolean isFirstRowRelative(); + /** + * @return last row relative or not + */ + public boolean isLastRowRelative(); +} \ No newline at end of file diff --git a/src/java/org/apache/poi/hssf/record/formula/AreaPtg.java b/src/java/org/apache/poi/hssf/record/formula/AreaPtg.java index 908c8d5e3..be34e0074 100644 --- a/src/java/org/apache/poi/hssf/record/formula/AreaPtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/AreaPtg.java @@ -34,14 +34,18 @@ import org.apache.poi.hssf.record.RecordInputStream; */ public class AreaPtg - extends Ptg + extends Ptg implements AreaI { public final static short sid = 0x25; private final static int SIZE = 9; - private short field_1_first_row; - private short field_2_last_row; - private short field_3_first_column; - private short field_4_last_column; + /** zero based, unsigned 16 bit */ + private int field_1_first_row; + /** zero based, unsigned 16 bit */ + private int field_2_last_row; + /** zero based, unsigned 8 bit */ + private int field_3_first_column; + /** zero based, unsigned 8 bit */ + private int field_4_last_column; private final static BitField rowRelative = BitFieldFactory.getInstance(0x8000); private final static BitField colRelative = BitFieldFactory.getInstance(0x4000); @@ -53,17 +57,25 @@ public class AreaPtg public AreaPtg(String arearef) { AreaReference ar = new AreaReference(arearef); - setFirstRow((short)ar.getCells()[0].getRow()); - setFirstColumn((short)ar.getCells()[0].getCol()); - setLastRow((short)ar.getCells()[1].getRow()); - setLastColumn((short)ar.getCells()[1].getCol()); - setFirstColRelative(!ar.getCells()[0].isColAbsolute()); - setLastColRelative(!ar.getCells()[1].isColAbsolute()); - setFirstRowRelative(!ar.getCells()[0].isRowAbsolute()); - setLastRowRelative(!ar.getCells()[1].isRowAbsolute()); + CellReference firstCell = ar.getFirstCell(); + CellReference lastCell = ar.getLastCell(); + setFirstRow(firstCell.getRow()); + setFirstColumn(firstCell.getCol()); + setLastRow(lastCell.getRow()); + setLastColumn(lastCell.getCol()); + setFirstColRelative(!firstCell.isColAbsolute()); + setLastColRelative(!lastCell.isColAbsolute()); + setFirstRowRelative(!firstCell.isRowAbsolute()); + setLastRowRelative(!lastCell.isRowAbsolute()); } - public AreaPtg(short firstRow, short lastRow, short firstColumn, short lastColumn, boolean firstRowRelative, boolean lastRowRelative, boolean firstColRelative, boolean lastColRelative) { + public AreaPtg(int firstRow, int lastRow, int firstColumn, int lastColumn, + boolean firstRowRelative, boolean lastRowRelative, boolean firstColRelative, boolean lastColRelative) { + + checkColumnBounds(firstColumn); + checkColumnBounds(lastColumn); + checkRowBounds(firstRow); + checkRowBounds(lastRow); setFirstRow(firstRow); setLastRow(lastRow); setFirstColumn(firstColumn); @@ -74,12 +86,23 @@ public class AreaPtg setLastColRelative(lastColRelative); } + private static void checkColumnBounds(int colIx) { + if((colIx & 0x0FF) != colIx) { + throw new IllegalArgumentException("colIx (" + colIx + ") is out of range"); + } + } + private static void checkRowBounds(int rowIx) { + if((rowIx & 0x0FFFF) != rowIx) { + throw new IllegalArgumentException("rowIx (" + rowIx + ") is out of range"); + } + } + public AreaPtg(RecordInputStream in) { - field_1_first_row = in.readShort(); - field_2_last_row = in.readShort(); - field_3_first_column = in.readShort(); - field_4_last_column = in.readShort(); + field_1_first_row = in.readUShort(); + field_2_last_row = in.readUShort(); + field_3_first_column = in.readUShort(); + field_4_last_column = in.readUShort(); //System.out.println(toString()); } @@ -108,10 +131,10 @@ public class AreaPtg public void writeBytes(byte [] array, int offset) { array[offset] = (byte) (sid + ptgClass); - LittleEndian.putShort(array,offset+1,field_1_first_row); - LittleEndian.putShort(array,offset+3,field_2_last_row); - LittleEndian.putShort(array,offset+5,field_3_first_column); - LittleEndian.putShort(array,offset+7,field_4_last_column); + LittleEndian.putShort(array,offset+1,(short)field_1_first_row); + LittleEndian.putShort(array,offset+3,(short)field_2_last_row); + LittleEndian.putShort(array,offset+5,(short)field_3_first_column); + LittleEndian.putShort(array,offset+7,(short)field_4_last_column); } public int getSize() @@ -122,42 +145,42 @@ public class AreaPtg /** * @return the first row in the area */ - public short getFirstRow() + public int getFirstRow() { return field_1_first_row; } /** * sets the first row - * @param row number (0-based) + * @param rowIx number (0-based) */ - public void setFirstRow(short row) - { - field_1_first_row = row; + public void setFirstRow(int rowIx) { + checkRowBounds(rowIx); + field_1_first_row = rowIx; } /** * @return last row in the range (x2 in x1,y1-x2,y2) */ - public short getLastRow() + public int getLastRow() { return field_2_last_row; } /** - * @param row last row number in the area + * @param rowIx last row number in the area */ - public void setLastRow(short row) - { - field_2_last_row = row; + public void setLastRow(int rowIx) { + checkRowBounds(rowIx); + field_2_last_row = rowIx; } /** * @return the first column number in the area. */ - public short getFirstColumn() + public int getFirstColumn() { - return columnMask.getShortValue(field_3_first_column); + return columnMask.getValue(field_3_first_column); } /** @@ -165,7 +188,7 @@ public class AreaPtg */ public short getFirstColumnRaw() { - return field_3_first_column; + return (short) field_3_first_column; // TODO } /** @@ -181,7 +204,7 @@ public class AreaPtg * @param rel is relative or not. */ public void setFirstRowRelative(boolean rel) { - field_3_first_column=rowRelative.setShortBoolean(field_3_first_column,rel); + field_3_first_column=rowRelative.setBoolean(field_3_first_column,rel); } /** @@ -196,21 +219,21 @@ public class AreaPtg * set whether the first column is relative */ public void setFirstColRelative(boolean rel) { - field_3_first_column=colRelative.setShortBoolean(field_3_first_column,rel); + field_3_first_column=colRelative.setBoolean(field_3_first_column,rel); } /** * set the first column in the area */ - public void setFirstColumn(short column) - { - field_3_first_column=columnMask.setShortValue(field_3_first_column, column); + public void setFirstColumn(int colIx) { + checkColumnBounds(colIx); + field_3_first_column=columnMask.setValue(field_3_first_column, colIx); } /** * set the first column irespective of the bitmasks */ - public void setFirstColumnRaw(short column) + public void setFirstColumnRaw(int column) { field_3_first_column = column; } @@ -218,9 +241,9 @@ public class AreaPtg /** * @return lastcolumn in the area */ - public short getLastColumn() + public int getLastColumn() { - return columnMask.getShortValue(field_4_last_column); + return columnMask.getValue(field_4_last_column); } /** @@ -228,7 +251,7 @@ public class AreaPtg */ public short getLastColumnRaw() { - return field_4_last_column; + return (short) field_4_last_column; } /** @@ -245,7 +268,7 @@ public class AreaPtg * false */ public void setLastRowRelative(boolean rel) { - field_4_last_column=rowRelative.setShortBoolean(field_4_last_column,rel); + field_4_last_column=rowRelative.setBoolean(field_4_last_column,rel); } /** @@ -260,16 +283,16 @@ public class AreaPtg * set whether the last column should be relative or not */ public void setLastColRelative(boolean rel) { - field_4_last_column=colRelative.setShortBoolean(field_4_last_column,rel); + field_4_last_column=colRelative.setBoolean(field_4_last_column,rel); } /** * set the last column in the area */ - public void setLastColumn(short column) - { - field_4_last_column=columnMask.setShortValue(field_4_last_column, column); + public void setLastColumn(int colIx) { + checkColumnBounds(colIx); + field_4_last_column=columnMask.setValue(field_4_last_column, colIx); } /** @@ -279,11 +302,20 @@ public class AreaPtg { field_4_last_column = column; } - + public String toFormulaString(Workbook book) { - return (new CellReference(getFirstRow(),getFirstColumn(),!isFirstRowRelative(),!isFirstColRelative())).toString() + ":" + - (new CellReference(getLastRow(),getLastColumn(),!isLastRowRelative(),!isLastColRelative())).toString(); + return toFormulaString(this, book); + } + protected static String toFormulaString(AreaI area, Workbook book) { + CellReference topLeft = new CellReference(area.getFirstRow(),area.getFirstColumn(),!area.isFirstRowRelative(),!area.isFirstColRelative()); + CellReference botRight = new CellReference(area.getLastRow(),area.getLastColumn(),!area.isLastRowRelative(),!area.isLastColRelative()); + + if(AreaReference.isWholeColumnReference(topLeft, botRight)) { + return (new AreaReference(topLeft, botRight)).formatAsString(); + } else { + return topLeft.formatAsString() + ":" + botRight.formatAsString(); + } } public byte getDefaultOperandClass() { diff --git a/src/java/org/apache/poi/hssf/record/formula/AreaVPtg.java b/src/java/org/apache/poi/hssf/record/formula/AreaVPtg.java index 2974eec64..42dc11fa3 100644 --- a/src/java/org/apache/poi/hssf/record/formula/AreaVPtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/AreaVPtg.java @@ -36,7 +36,7 @@ import org.apache.poi.hssf.model.Workbook; * @author Jason Height (jheight at chariot dot net dot au) */ -public class AreaVPtg +public final class AreaVPtg extends AreaPtg { public final static short sid = 0x45; @@ -45,7 +45,7 @@ public class AreaVPtg //Required for clone methods } - public AreaVPtg(short firstRow, short lastRow, short firstColumn, short lastColumn, boolean firstRowRelative, boolean lastRowRelative, boolean firstColRelative, boolean lastColRelative) { + public AreaVPtg(int firstRow, int lastRow, int firstColumn, int lastColumn, boolean firstRowRelative, boolean lastRowRelative, boolean firstColRelative, boolean lastColRelative) { super(firstRow, lastRow, firstColumn, lastColumn, firstRowRelative, lastRowRelative, firstColRelative, lastColRelative); } diff --git a/src/java/org/apache/poi/hssf/record/formula/ArrayPtg.java b/src/java/org/apache/poi/hssf/record/formula/ArrayPtg.java index 372b1850e..12166b796 100644 --- a/src/java/org/apache/poi/hssf/record/formula/ArrayPtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/ArrayPtg.java @@ -72,8 +72,10 @@ public class ArrayPtg extends Ptg field_7_reserved = in.readByte(); } - /** Read in the actual token (array) values. This occurs AFTER the last - * Ptg in the expression. + /** + * Read in the actual token (array) values. This occurs + * AFTER the last Ptg in the expression. + * See page 304-305 of Excel97-2007BinaryFileFormat(xls)Specification.pdf */ public void readTokenValues(RecordInputStream in) { token_1_columns = (short)(0x00ff & in.readByte()); @@ -88,18 +90,17 @@ public class ArrayPtg extends Ptg token_3_arrayValues = new Object[token_1_columns][token_2_rows]; for (int x=0;x#NULL! - Intersection of two cell ranges is empty */ + public static final ErrPtg NULL_INTERSECTION = new ErrPtg(EC.ERROR_NULL); + /** #DIV/0! - Division by zero */ + public static final ErrPtg DIV_ZERO = new ErrPtg(EC.ERROR_DIV_0); + /** #VALUE! - Wrong type of operand */ + public static final ErrPtg VALUE_INVALID = new ErrPtg(EC.ERROR_VALUE); + /** #REF! - Illegal or deleted cell reference */ + public static final ErrPtg REF_INVALID = new ErrPtg(EC.ERROR_REF); + /** #NAME? - Wrong function or range name */ + public static final ErrPtg NAME_INVALID = new ErrPtg(EC.ERROR_NAME); + /** #NUM! - Value range overflow */ + public static final ErrPtg NUM_ERROR = new ErrPtg(EC.ERROR_NUM); + /** #N/A - Argument or function not available */ + public static final ErrPtg N_A = new ErrPtg(EC.ERROR_NA); + + public static final short sid = 0x1c; private static final int SIZE = 2; - private byte field_1_error_code; + private int field_1_error_code; /** Creates new ErrPtg */ - public ErrPtg() - { + public ErrPtg(int errorCode) { + if(!HSSFErrorConstants.isValidCode(errorCode)) { + throw new IllegalArgumentException("Invalid error code (" + errorCode + ")"); + } + field_1_error_code = errorCode; } - - public ErrPtg(RecordInputStream in) - { - field_1_error_code = in.readByte(); + + public ErrPtg(RecordInputStream in) { + this(in.readByte()); } public void writeBytes(byte [] array, int offset) { array[offset] = (byte) (sid + ptgClass); - array[offset + 1] = field_1_error_code; + array[offset + 1] = (byte)field_1_error_code; } - public String toFormulaString(Workbook book) - { - switch(field_1_error_code) - { - case HSSFErrorConstants.ERROR_NULL: - return "#NULL!"; - case HSSFErrorConstants.ERROR_DIV_0: - return "#DIV/0!"; - case HSSFErrorConstants.ERROR_VALUE: - return "#VALUE!"; - case HSSFErrorConstants.ERROR_REF: - return "#REF!"; - case HSSFErrorConstants.ERROR_NAME: - return "#NAME?"; - case HSSFErrorConstants.ERROR_NUM: - return "#NUM!"; - case HSSFErrorConstants.ERROR_NA: - return "#N/A"; - } - - // Shouldn't happen anyway. Excel docs say that this is returned for all other codes. - return "#N/A"; + public String toFormulaString(Workbook book) { + return HSSFErrorConstants.getText(field_1_error_code); } - public int getSize() - { + public int getSize() { return SIZE; } - public byte getDefaultOperandClass() - { + public byte getDefaultOperandClass() { return Ptg.CLASS_VALUE; } public Object clone() { - ErrPtg ptg = new ErrPtg(); - ptg.field_1_error_code = field_1_error_code; - return ptg; + return new ErrPtg(field_1_error_code); + } + + public int getErrorCode() { + return field_1_error_code; } } diff --git a/src/java/org/apache/poi/hssf/record/formula/FuncPtg.java b/src/java/org/apache/poi/hssf/record/formula/FuncPtg.java index 410364971..1123fc803 100644 --- a/src/java/org/apache/poi/hssf/record/formula/FuncPtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/FuncPtg.java @@ -62,6 +62,10 @@ public class FuncPtg extends AbstractFunctionPtg{ numParams=0; } + } + public FuncPtg(int functionIndex, int numberOfParameters) { + field_2_fnc_index = (short) functionIndex; + numParams = numberOfParameters; } public void writeBytes(byte[] array, int offset) { diff --git a/src/java/org/apache/poi/hssf/record/formula/IntPtg.java b/src/java/org/apache/poi/hssf/record/formula/IntPtg.java index 257c089df..f4106b6aa 100644 --- a/src/java/org/apache/poi/hssf/record/formula/IntPtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/IntPtg.java @@ -1,4 +1,3 @@ - /* ==================================================================== Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with @@ -16,12 +15,6 @@ limitations under the License. ==================================================================== */ - -/* - * IntPtg.java - * - * Created on October 29, 2001, 7:37 PM - */ package org.apache.poi.hssf.record.formula; import org.apache.poi.util.LittleEndian; @@ -29,64 +22,45 @@ import org.apache.poi.hssf.model.Workbook; import org.apache.poi.hssf.record.RecordInputStream; /** - * Integer (unsigned short intger) + * Integer (unsigned short integer) * Stores an unsigned short value (java int) in a formula * @author Andrew C. Oliver (acoliver at apache dot org) * @author Jason Height (jheight at chariot dot net dot au) */ +public final class IntPtg extends Ptg { + // 16 bit unsigned integer + private static final int MIN_VALUE = 0x0000; + private static final int MAX_VALUE = 0xFFFF; + + /** + * Excel represents integers 0..65535 with the tInt token. + * @return true if the specified value is within the range of values + * IntPtg can represent. + */ + public static boolean isInRange(int i) { + return i>=MIN_VALUE && i <=MAX_VALUE; + } -public class IntPtg - extends Ptg -{ public final static int SIZE = 3; public final static byte sid = 0x1e; private int field_1_value; - private IntPtg() { - //Required for clone methods + public IntPtg(RecordInputStream in) { + this(in.readUShort()); } - public IntPtg(RecordInputStream in) - { - setValue(in.readUShort()); - } - - - // IntPtg should be able to create itself, shouldnt have to call setValue - public IntPtg(String formulaToken) { - setValue(Integer.parseInt(formulaToken)); - } - /** - * Sets the wrapped value. - * Normally you should call with a positive int. - */ - public void setValue(int value) - { - if(value < 0 || value > (Short.MAX_VALUE + 1)*2 ) - throw new IllegalArgumentException("Unsigned short is out of range: " + value); + public IntPtg(int value) { + if(!isInRange(value)) { + throw new IllegalArgumentException("value is out of range: " + value); + } field_1_value = value; } - /** - * Returns the value as a short, which may have - * been wrapped into negative numbers - */ - public int getValue() - { + public int getValue() { return field_1_value; } - /** - * Returns the value as an unsigned positive int. - */ - public int getValueAsInt() - { - if(field_1_value < 0) { - return (Short.MAX_VALUE + 1)*2 + field_1_value; - } - return field_1_value; - } public void writeBytes(byte [] array, int offset) { @@ -94,20 +68,25 @@ public class IntPtg LittleEndian.putUShort(array, offset + 1, getValue()); } - public int getSize() - { + public int getSize() { return SIZE; } - public String toFormulaString(Workbook book) - { - return "" + getValue(); + public String toFormulaString(Workbook book) { + return String.valueOf(getValue()); + } + public byte getDefaultOperandClass() { + return Ptg.CLASS_VALUE; } - public byte getDefaultOperandClass() {return Ptg.CLASS_VALUE;} - public Object clone() { - IntPtg ptg = new IntPtg(); - ptg.field_1_value = field_1_value; - return ptg; - } + public Object clone() { + return new IntPtg(field_1_value); + } + public String toString() { + StringBuffer sb = new StringBuffer(64); + sb.append(getClass().getName()).append(" ["); + sb.append(field_1_value); + sb.append("]"); + return sb.toString(); + } } diff --git a/src/java/org/apache/poi/hssf/record/formula/NamePtg.java b/src/java/org/apache/poi/hssf/record/formula/NamePtg.java index d418afa7e..5405481a0 100644 --- a/src/java/org/apache/poi/hssf/record/formula/NamePtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/NamePtg.java @@ -33,6 +33,7 @@ public class NamePtg { public final static short sid = 0x23; private final static int SIZE = 5; + /** one-based index to defined name record */ private short field_1_label_index; private short field_2_zero; // reserved must be 0 boolean xtra=false; @@ -42,24 +43,32 @@ public class NamePtg //Required for clone methods } - /** Creates new NamePtg */ - - public NamePtg(String name, Workbook book) - { - final short n = (short) (book.getNumNames() + 1); + /** + * Creates new NamePtg and sets its name index to that of the corresponding defined name record + * in the workbook. The search for the name record is case insensitive. If it is not found, + * it gets created. + */ + public NamePtg(String name, Workbook book) { + field_1_label_index = (short)(1+getOrCreateNameRecord(book, name)); // convert to 1-based + } + /** + * @return zero based index of the found or newly created defined name record. + */ + private static final int getOrCreateNameRecord(Workbook book, String name) { + // perhaps this logic belongs in Workbook + int countNames = book.getNumNames(); NameRecord rec; - for (short i = 1; i < n; i++) { - rec = book.getNameRecord(i - 1); - if (name.equals(rec.getNameText())) { - field_1_label_index = i; - return; + for (int i = 0; i < countNames; i++) { + rec = book.getNameRecord(i); + if (name.equalsIgnoreCase(rec.getNameText())) { + return i; } } rec = new NameRecord(); rec.setNameText(name); rec.setNameTextLength((byte) name.length()); book.addName(rec); - field_1_label_index = n; + return countNames; } /** Creates new NamePtg */ @@ -71,6 +80,13 @@ public class NamePtg field_2_zero = in.readShort(); //if (data[offset+6]==0) xtra=true; } + + /** + * @return zero based index to a defined name record in the LinkTable + */ + public int getIndex() { + return field_1_label_index-1; // convert to zero based + } public void writeBytes(byte [] array, int offset) { diff --git a/src/java/org/apache/poi/hssf/record/formula/NameXPtg.java b/src/java/org/apache/poi/hssf/record/formula/NameXPtg.java index b1f280a01..ccf5ab6fc 100644 --- a/src/java/org/apache/poi/hssf/record/formula/NameXPtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/NameXPtg.java @@ -25,13 +25,11 @@ import org.apache.poi.hssf.record.RecordInputStream; * * @author aviks */ - -public class NameXPtg extends Ptg -{ +public final class NameXPtg extends Ptg { public final static short sid = 0x39; private final static int SIZE = 7; - private short field_1_ixals; // index to externsheet record - private short field_2_ilbl; //index to name or externname table(1 based) + private short field_1_ixals; // index to REF entry in externsheet record + private short field_2_ilbl; //index to defined name or externname table(1 based) private short field_3_reserved; // reserved must be 0 @@ -41,13 +39,6 @@ public class NameXPtg extends Ptg /** Creates new NamePtg */ - public NameXPtg(String name) - { - //TODO - } - - /** Creates new NamePtg */ - public NameXPtg(RecordInputStream in) { field_1_ixals = in.readShort(); @@ -72,7 +63,8 @@ public class NameXPtg extends Ptg public String toFormulaString(Workbook book) { - return "NO IDEA - NAME"; + // -1 to convert definedNameIndex from 1-based to zero-based + return book.resolveNameXText(field_1_ixals, field_2_ilbl-1); } public byte getDefaultOperandClass() {return Ptg.CLASS_VALUE;} diff --git a/src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java b/src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java index 510eebb03..84ff659b3 100644 --- a/src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java @@ -18,16 +18,14 @@ package org.apache.poi.hssf.record.formula; -import org.apache.poi.util.LittleEndian; - -import org.apache.poi.hssf.util.RangeAddress; -import org.apache.poi.hssf.util.CellReference; -import org.apache.poi.hssf.util.SheetReferences; -import org.apache.poi.hssf.model.Workbook; -import org.apache.poi.util.BitField; -import org.apache.poi.util.BitFieldFactory; import org.apache.poi.hssf.model.Workbook; import org.apache.poi.hssf.record.RecordInputStream; +import org.apache.poi.hssf.util.CellReference; +import org.apache.poi.hssf.util.RangeAddress; +import org.apache.poi.hssf.util.SheetReferences; +import org.apache.poi.util.BitField; +import org.apache.poi.util.BitFieldFactory; +import org.apache.poi.util.LittleEndian; /** * Title: Reference 3D Ptg

    @@ -42,8 +40,14 @@ public class Ref3DPtg extends Ptg { public final static byte sid = 0x3a; private final static int SIZE = 7; // 6 + 1 for Ptg private short field_1_index_extern_sheet; - private short field_2_row; - private short field_3_column; + /** The row index - zero based unsigned 16 bit value */ + private int field_2_row; + /** Field 2 + * - lower 8 bits is the zero based unsigned byte column index + * - bit 16 - isRowRelative + * - bit 15 - isColumnRelative + */ + private int field_3_column; private BitField rowRelative = BitFieldFactory.getInstance(0x8000); private BitField colRelative = BitFieldFactory.getInstance(0x4000); @@ -58,8 +62,8 @@ public class Ref3DPtg extends Ptg { public Ref3DPtg(String cellref, short externIdx ) { CellReference c= new CellReference(cellref); - setRow((short) c.getRow()); - setColumn((short) c.getCol()); + setRow(c.getRow()); + setColumn(c.getCol()); setColRelative(!c.isColAbsolute()); setRowRelative(!c.isRowAbsolute()); setExternSheetIndex(externIdx); @@ -81,8 +85,8 @@ public class Ref3DPtg extends Ptg { public void writeBytes(byte [] array, int offset) { array[ 0 + offset ] = (byte) (sid + ptgClass); LittleEndian.putShort(array, 1 + offset , getExternSheetIndex()); - LittleEndian.putShort(array, 3 + offset , getRow()); - LittleEndian.putShort(array, 5 + offset , getColumnRaw()); + LittleEndian.putShort(array, 3 + offset , (short)getRow()); + LittleEndian.putShort(array, 5 + offset , (short)getColumnRaw()); } public int getSize() { @@ -97,19 +101,19 @@ public class Ref3DPtg extends Ptg { field_1_index_extern_sheet = index; } - public short getRow() { + public int getRow() { return field_2_row; } - public void setRow(short row) { + public void setRow(int row) { field_2_row = row; } - public short getColumn() { - return ( short ) (field_3_column & 0xFF); + public int getColumn() { + return field_3_column & 0xFF; } - public short getColumnRaw() { + public int getColumnRaw() { return field_3_column; } @@ -119,7 +123,7 @@ public class Ref3DPtg extends Ptg { } public void setRowRelative(boolean rel) { - field_3_column=rowRelative.setShortBoolean(field_3_column,rel); + field_3_column=rowRelative.setBoolean(field_3_column,rel); } public boolean isColRelative() @@ -128,7 +132,7 @@ public class Ref3DPtg extends Ptg { } public void setColRelative(boolean rel) { - field_3_column=colRelative.setShortBoolean(field_3_column,rel); + field_3_column=colRelative.setBoolean(field_3_column,rel); } public void setColumn(short column) { field_3_column &= 0xFF00; @@ -183,7 +187,7 @@ public class Ref3DPtg extends Ptg { SheetNameFormatter.appendFormat(retval, sheetName); retval.append( '!' ); } - retval.append((new CellReference(getRow(),getColumn(),!isRowRelative(),!isColRelative())).toString()); + retval.append((new CellReference(getRow(),getColumn(),!isRowRelative(),!isColRelative())).formatAsString()); return retval.toString(); } @@ -197,5 +201,4 @@ public class Ref3DPtg extends Ptg { ptg.setClass(ptgClass); return ptg; } - } diff --git a/src/java/org/apache/poi/hssf/record/formula/RefAPtg.java b/src/java/org/apache/poi/hssf/record/formula/RefAPtg.java index 996f40e39..596b38623 100644 --- a/src/java/org/apache/poi/hssf/record/formula/RefAPtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/RefAPtg.java @@ -1,4 +1,3 @@ - /* ==================================================================== Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with @@ -16,34 +15,23 @@ limitations under the License. ==================================================================== */ -/* - * ValueReferencePtg.java - * - * Created on November 21, 2001, 5:27 PM - */ package org.apache.poi.hssf.record.formula; -import org.apache.poi.util.LittleEndian; -import org.apache.poi.util.BitField; - import org.apache.poi.hssf.record.RecordInputStream; -import org.apache.poi.hssf.util.CellReference; -import org.apache.poi.hssf.model.Workbook; /** * RefNAPtg * @author Jason Height (jheight at chariot dot net dot au) */ -public class RefAPtg extends ReferencePtg -{ +public final class RefAPtg extends ReferencePtg { public final static byte sid = 0x64; protected RefAPtg() { super(); } - public RefAPtg(short row, short column, boolean isRowRelative, boolean isColumnRelative) { + public RefAPtg(int row, int column, boolean isRowRelative, boolean isColumnRelative) { super(row, column, isRowRelative, isColumnRelative); } diff --git a/src/java/org/apache/poi/hssf/record/formula/RefVPtg.java b/src/java/org/apache/poi/hssf/record/formula/RefVPtg.java index b9d55a09e..8a6b2c03b 100644 --- a/src/java/org/apache/poi/hssf/record/formula/RefVPtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/RefVPtg.java @@ -1,4 +1,3 @@ - /* ==================================================================== Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with @@ -18,27 +17,20 @@ package org.apache.poi.hssf.record.formula; -import org.apache.poi.util.LittleEndian; -import org.apache.poi.util.BitField; - import org.apache.poi.hssf.record.RecordInputStream; -import org.apache.poi.hssf.util.CellReference; -import org.apache.poi.hssf.model.Workbook; /** * RefVPtg * @author Jason Height (jheight at chariot dot net dot au) */ - -public class RefVPtg extends ReferencePtg -{ +public final class RefVPtg extends ReferencePtg { public final static byte sid = 0x44; protected RefVPtg() { super(); } - public RefVPtg(short row, short column, boolean isRowRelative, boolean isColumnRelative) { + public RefVPtg(int row, int column, boolean isRowRelative, boolean isColumnRelative) { super(row, column, isRowRelative, isColumnRelative); } diff --git a/src/java/org/apache/poi/hssf/record/formula/ReferencePtg.java b/src/java/org/apache/poi/hssf/record/formula/ReferencePtg.java index df3e5a70b..4983c9d07 100644 --- a/src/java/org/apache/poi/hssf/record/formula/ReferencePtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/ReferencePtg.java @@ -31,26 +31,22 @@ import org.apache.poi.hssf.record.RecordInputStream; * @author Jason Height (jheight at chariot dot net dot au) */ -public class ReferencePtg extends Ptg -{ +public class ReferencePtg extends Ptg { private final static int SIZE = 5; public final static byte sid = 0x24; private final static int MAX_ROW_NUMBER = 65536; - //public final static byte sid = 0x44; - /** - * The row number, between 0 and 65535, but stored as a signed - * short between -32767 and 32768. - * Take care about which version you fetch back! + /** The row index - zero based unsigned 16 bit value */ + private int field_1_row; + /** Field 2 + * - lower 8 bits is the zero based unsigned byte column index + * - bit 16 - isRowRelative + * - bit 15 - isColumnRelative */ - private short field_1_row; - /** - * The column number, between 0 and ?? - */ - private short field_2_col; - private BitField rowRelative = BitFieldFactory.getInstance(0x8000); - private BitField colRelative = BitFieldFactory.getInstance(0x4000); - private BitField column = BitFieldFactory.getInstance(0x3FFF); + private int field_2_col; + private static final BitField rowRelative = BitFieldFactory.getInstance(0x8000); + private static final BitField colRelative = BitFieldFactory.getInstance(0x4000); + private static final BitField column = BitFieldFactory.getInstance(0x00FF); protected ReferencePtg() { //Required for clone methods @@ -62,13 +58,13 @@ public class ReferencePtg extends Ptg */ public ReferencePtg(String cellref) { CellReference c= new CellReference(cellref); - setRow((short) c.getRow()); - setColumn((short) c.getCol()); + setRow(c.getRow()); + setColumn(c.getCol()); setColRelative(!c.isColAbsolute()); setRowRelative(!c.isRowAbsolute()); } - public ReferencePtg(short row, short column, boolean isRowRelative, boolean isColumnRelative) { + public ReferencePtg(int row, int column, boolean isRowRelative, boolean isColumnRelative) { setRow(row); setColumn(column); setRowRelative(isRowRelative); @@ -79,8 +75,8 @@ public class ReferencePtg extends Ptg public ReferencePtg(RecordInputStream in) { - field_1_row = in.readShort(); - field_2_col = in.readShort(); + field_1_row = in.readUShort(); + field_2_col = in.readUShort(); } public String getRefPtgName() { @@ -104,33 +100,23 @@ public class ReferencePtg extends Ptg { array[offset] = (byte) (sid + ptgClass); - LittleEndian.putShort(array,offset+1,field_1_row); - LittleEndian.putShort(array,offset+3,field_2_col); + LittleEndian.putShort(array, offset+1, (short)field_1_row); + LittleEndian.putShort(array, offset+3, (short)field_2_col); } - public void setRow(short row) - { - field_1_row = row; - } public void setRow(int row) { if(row < 0 || row >= MAX_ROW_NUMBER) { throw new IllegalArgumentException("The row number, when specified as an integer, must be between 0 and " + MAX_ROW_NUMBER); } - - // Save, wrapping as needed - if(row > Short.MAX_VALUE) { - field_1_row = (short)(row - MAX_ROW_NUMBER); - } else { - field_1_row = (short)row; - } + field_1_row = row; } /** * Returns the row number as a short, which will be * wrapped (negative) for values between 32769 and 65535 */ - public short getRow() + public int getRow() { return field_1_row; } @@ -151,7 +137,7 @@ public class ReferencePtg extends Ptg } public void setRowRelative(boolean rel) { - field_2_col=rowRelative.setShortBoolean(field_2_col,rel); + field_2_col=rowRelative.setBoolean(field_2_col,rel); } public boolean isColRelative() @@ -160,27 +146,29 @@ public class ReferencePtg extends Ptg } public void setColRelative(boolean rel) { - field_2_col=colRelative.setShortBoolean(field_2_col,rel); + field_2_col=colRelative.setBoolean(field_2_col,rel); } - public void setColumnRaw(short col) + public void setColumnRaw(int col) { field_2_col = col; } - public short getColumnRaw() + public int getColumnRaw() { return field_2_col; } - public void setColumn(short col) + public void setColumn(int col) { - field_2_col = column.setShortValue(field_2_col, col); + if(col < 0 || col > 0x100) { + throw new IllegalArgumentException("Specified colIx (" + col + ") is out of range"); + } + field_2_col = column.setValue(field_2_col, col); } - public short getColumn() - { - return column.getShortValue(field_2_col); + public int getColumn() { + return column.getValue(field_2_col); } public int getSize() @@ -191,7 +179,7 @@ public class ReferencePtg extends Ptg public String toFormulaString(Workbook book) { //TODO -- should we store a cellreference instance in this ptg?? but .. memory is an issue, i believe! - return (new CellReference(getRowAsInt(),getColumn(),!isRowRelative(),!isColRelative())).toString(); + return (new CellReference(getRowAsInt(),getColumn(),!isRowRelative(),!isColRelative())).formatAsString(); } public byte getDefaultOperandClass() { diff --git a/src/java/org/apache/poi/hssf/record/formula/SheetNameFormatter.java b/src/java/org/apache/poi/hssf/record/formula/SheetNameFormatter.java index ba796db3b..8e47cbe7a 100755 --- a/src/java/org/apache/poi/hssf/record/formula/SheetNameFormatter.java +++ b/src/java/org/apache/poi/hssf/record/formula/SheetNameFormatter.java @@ -26,7 +26,7 @@ import java.util.regex.Pattern; * * @author Josh Micich */ -final class SheetNameFormatter { +public final class SheetNameFormatter { private static final String BIFF8_LAST_COLUMN = "IV"; private static final int BIFF8_LAST_COLUMN_TEXT_LEN = BIFF8_LAST_COLUMN.length(); diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java b/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java index 67f455797..f906e91a4 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java @@ -456,7 +456,7 @@ public class HSSFCell implements Cell boolRec.setColumn(col); if (setValue) { - boolRec.setValue(getBooleanCellValue()); + boolRec.setValue(convertCellValueToBoolean()); } boolRec.setXFIndex(styleIndex); boolRec.setRow(row); @@ -644,7 +644,7 @@ public class HSSFCell implements Cell //only set to default if there is no extended format index already set if (rec.getXFIndex() == (short)0) rec.setXFIndex(( short ) 0x0f); - FormulaParser fp = new FormulaParser(formula+";",book); + FormulaParser fp = new FormulaParser(formula, book); fp.parse(); Ptg[] ptg = fp.getRPNPtg(); int size = 0; @@ -830,6 +830,34 @@ public class HSSFCell implements Cell } (( BoolErrRecord ) record).setValue(value); } + /** + * Chooses a new boolean value for the cell when its type is changing.

    + * + * 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(); + case CELL_TYPE_STRING: + return Boolean.valueOf(((StringRecord)record).getString()).booleanValue(); + case CELL_TYPE_NUMERIC: + return ((NumberRecord)record).getValue() != 0; + + // All other cases convert to false + // These choices are not well justified. + case CELL_TYPE_FORMULA: + // should really evaluate, but HSSFCell can't call HSSFFormulaEvaluator + case CELL_TYPE_ERROR: + case CELL_TYPE_BLANK: + return false; + } + throw new RuntimeException("Unexpected cell type (" + cellType + ")"); + } /** * get the value of the cell as a boolean. For strings, numbers, and errors, we throw an exception. diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFErrorConstants.java b/src/java/org/apache/poi/hssf/usermodel/HSSFErrorConstants.java index 1f5ec13c3..89c25d1e8 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFErrorConstants.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFErrorConstants.java @@ -15,26 +15,68 @@ limitations under the License. ==================================================================== */ - -/* - * HSSFErrorConstants.java - * - * Created on January 19, 2002, 9:30 AM - */ package org.apache.poi.hssf.usermodel; /** - * contains constants representing Excel error codes. + * Contains raw Excel error codes (as defined in OOO's excelfileformat.pdf (2.5.6) + * * @author Michael Harhen */ - -public interface HSSFErrorConstants -{ - public static final byte ERROR_NULL = 0x00; // #NULL! - public static final byte ERROR_DIV_0 = 0x07; // #DIV/0! - public static final byte ERROR_VALUE = 0x0f; // #VALUE! - public static final byte ERROR_REF = 0x17; // #REF! - public static final byte ERROR_NAME = 0x1d; // #NAME? - public static final byte ERROR_NUM = 0x24; // #NUM! - public static final byte ERROR_NA = 0x2a; // #N/A +public final class HSSFErrorConstants { + private HSSFErrorConstants() { + // no instances of this class + } + + /** #NULL! - Intersection of two cell ranges is empty */ + public static final int ERROR_NULL = 0x00; + /** #DIV/0! - Division by zero */ + public static final int ERROR_DIV_0 = 0x07; + /** #VALUE! - Wrong type of operand */ + public static final int ERROR_VALUE = 0x0F; + /** #REF! - Illegal or deleted cell reference */ + public static final int ERROR_REF = 0x17; + /** #NAME? - Wrong function or range name */ + public static final int ERROR_NAME = 0x1D; + /** #NUM! - Value range overflow */ + public static final int ERROR_NUM = 0x24; + /** #N/A - Argument or function not available */ + public static final int ERROR_NA = 0x2A; + + + /** + * @return Standard Excel error literal for the specified error code. + * @throws IllegalArgumentException if the specified error code is not one of the 7 + * standard error codes + */ + public static final String getText(int errorCode) { + switch(errorCode) { + case ERROR_NULL: return "#NULL!"; + case ERROR_DIV_0: return "#DIV/0!"; + case ERROR_VALUE: return "#VALUE!"; + case ERROR_REF: return "#REF!"; + case ERROR_NAME: return "#NAME?"; + case ERROR_NUM: return "#NUM!"; + case ERROR_NA: return "#N/A"; + } + throw new IllegalArgumentException("Bad error code (" + errorCode + ")"); + } + + /** + * @return true if the specified error code is a standard Excel error code. + */ + public static final boolean isValidCode(int errorCode) { + // This method exists because it would be bad to force clients to catch + // IllegalArgumentException if there were potential for passing an invalid error code. + switch(errorCode) { + case ERROR_NULL: + case ERROR_DIV_0: + case ERROR_VALUE: + case ERROR_REF: + case ERROR_NAME: + case ERROR_NUM: + case ERROR_NA: + return true; + } + return false; + } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFPalette.java b/src/java/org/apache/poi/hssf/usermodel/HSSFPalette.java index 42773d4a3..0a3172889 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFPalette.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFPalette.java @@ -100,9 +100,11 @@ public class HSSFPalette implements Palette for (short i = (short) PaletteRecord.FIRST_COLOR_INDEX; b != null; b = palette.getColor(++i)) { - int colorDistance = red - b[0] + green - b[1] + blue - b[2]; + int colorDistance = Math.abs(red - b[0]) + + Math.abs(green - b[1]) + Math.abs(blue - b[2]); if (colorDistance < minColorDistance) { + minColorDistance = colorDistance; result = getColor(i); } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFRow.java b/src/java/org/apache/poi/hssf/usermodel/HSSFRow.java index 0c9807b5a..54229a16a 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFRow.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFRow.java @@ -159,28 +159,25 @@ public class HSSFRow * @param cell to remove */ public void removeCell(Cell cell) { - removeCell(cell, true); + removeCell((HSSFCell) cell, true); } - - private void removeCell(Cell cell, boolean alsoRemoveRecords) { - - HSSFCell hcell = (HSSFCell) cell; + private void removeCell(HSSFCell cell, boolean alsoRemoveRecords) { if(alsoRemoveRecords) { - CellValueRecordInterface cval = hcell.getCellValueRecord(); + CellValueRecordInterface cval = cell.getCellValueRecord(); sheet.removeValueRecord(getRowNum(), cval); } - short column=hcell.getCellNum(); - if(hcell!=null && column + * The area reference must be contiguous (i.e. represent a single rectangle, not a union of rectangles) */ public AreaReference(String reference) { if(! isContiguous(reference)) { - throw new IllegalArgumentException("References passed to the AreaReference must be contiguous, use generateContiguous(ref) if you have non-contiguous references"); + throw new IllegalArgumentException( + "References passed to the AreaReference must be contiguous, " + + "use generateContiguous(ref) if you have non-contiguous references"); } - String[] refs = seperateAreaRefs(reference); - dim = refs.length; - cells = new CellReference[dim]; - for (int i=0;i= 'A' && parts[0].charAt(0) <= 'Z' && + parts[1].charAt(0) >= 'A' && parts[1].charAt(0) <= 'Z') { + // Represented internally as x$1 to x$65536 + // which is the maximum range of rows + parts[0] = parts[0] + "$1"; + parts[1] = parts[1] + "$65536"; } + + _firstCell = new CellReference(parts[0]); + + if(parts.length == 2) { + _lastCell = new CellReference(parts[1]); + _isSingleCell = false; + } else { + _lastCell = _firstCell; + _isSingleCell = true; + } + } + + /** + * Creates an area ref from a pair of Cell References. + */ + public AreaReference(CellReference topLeft, CellReference botRight) { + _firstCell = topLeft; + _lastCell = botRight; + _isSingleCell = false; } /** @@ -57,6 +92,24 @@ private int dim; } return false; } + + /** + * Is the reference for a whole-column reference, + * such as C:C or D:G ? + */ + public static boolean isWholeColumnReference(CellReference topLeft, CellReference botRight) { + // These are represented as something like + // C$1:C$65535 or D$1:F$0 + // i.e. absolute from 1st row to 0th one + if(topLeft.getRow() == 0 && topLeft.isRowAbsolute() && + botRight.getRow() == 65535 && botRight.isRowAbsolute()) { + return true; + } + return false; + } + public boolean isWholeColumnReference() { + return isWholeColumnReference(_firstCell, _lastCell); + } /** * Takes a non-contiguous area reference, and @@ -73,75 +126,171 @@ private int dim; return (AreaReference[])refs.toArray(new AreaReference[refs.size()]); } - //not sure if we need to be flexible here! - /** return the dimensions of this area - **/ - public int getDim() { - return dim; - } - /** - * Return the cell references that define this area - * (i.e. the two corners) + /** + * @return false if this area reference involves more than one cell */ - public CellReference[] getCells() { - return cells; + public boolean isSingleCell() { + return _isSingleCell; + } + + /** + * @return the first cell reference which defines this area. Usually this cell is in the upper + * left corner of the area (but this is not a requirement). + */ + public CellReference getFirstCell() { + return _firstCell; + } + + /** + * Note - if this area reference refers to a single cell, the return value of this method will + * be identical to that of getFirstCell() + * @return the second cell reference which defines this area. For multi-cell areas, this is + * cell diagonally opposite the 'first cell'. Usually this cell is in the lower right corner + * of the area (but this is not a requirement). + */ + public CellReference getLastCell() { + return _lastCell; } /** * Returns a reference to every cell covered by this area */ public CellReference[] getAllReferencedCells() { // Special case for single cell reference - if(cells.length == 1) { - return cells; + if(_isSingleCell) { + return new CellReference[] { _firstCell, }; } + // Interpolate between the two - int minRow = Math.min(cells[0].getRow(), cells[1].getRow()); - int maxRow = Math.max(cells[0].getRow(), cells[1].getRow()); - int minCol = Math.min(cells[0].getCol(), cells[1].getCol()); - int maxCol = Math.max(cells[0].getCol(), cells[1].getCol()); + int minRow = Math.min(_firstCell.getRow(), _lastCell.getRow()); + int maxRow = Math.max(_firstCell.getRow(), _lastCell.getRow()); + int minCol = Math.min(_firstCell.getCol(), _lastCell.getCol()); + int maxCol = Math.max(_firstCell.getCol(), _lastCell.getCol()); + String sheetName = _firstCell.getSheetName(); ArrayList refs = new ArrayList(); for(int row=minRow; row<=maxRow; row++) { for(int col=minCol; col<=maxCol; col++) { - CellReference ref = new CellReference(row, col, cells[0].isRowAbsolute(), cells[0].isColAbsolute()); - ref.setSheetName(cells[0].getSheetName()); + CellReference ref = new CellReference(sheetName, row, col, _firstCell.isRowAbsolute(), _firstCell.isColAbsolute()); refs.add(ref); } } return (CellReference[])refs.toArray(new CellReference[refs.size()]); } - public String toString() { - StringBuffer retval = new StringBuffer(); - for (int i=0;i + * ResultComment + * A1:A1Single cell area reference without sheet + * A1:$C$1Multi-cell area reference without sheet + * Sheet1!A$1:B4Standard sheet name + * 'O''Brien''s Sales'!B5:C6' Sheet name with special characters + * + * @return the text representation of this area reference as it would appear in a formula. + */ + public String formatAsString() { + // Special handling for whole-column references + if(isWholeColumnReference()) { + return + CellReference.convertNumToColString(_firstCell.getCol()) + + ":" + + CellReference.convertNumToColString(_lastCell.getCol()); + } + + StringBuffer sb = new StringBuffer(32); + sb.append(_firstCell.formatAsString()); + if(!_isSingleCell) { + sb.append(CELL_DELIMITER); + if(_lastCell.getSheetName() == null) { + sb.append(_lastCell.formatAsString()); + } else { + // don't want to include the sheet name twice + _lastCell.appendCellReference(sb); + } } - retval.deleteCharAt(0); - return retval.toString(); + return sb.toString(); + } + public String toString() { + StringBuffer sb = new StringBuffer(64); + sb.append(getClass().getName()).append(" ["); + sb.append(formatAsString()); + sb.append("]"); + return sb.toString(); } /** - * seperates Area refs in two parts and returns them as seperate elements in a - * String array + * Separates Area refs in two parts and returns them as separate elements in a String array, + * each qualified with the sheet name (if present) + * + * @return array with one or two elements. never null */ - private String[] seperateAreaRefs(String reference) { - String[] retval = null; - - int length = reference.length(); - - int loc = reference.indexOf(':',0); - if(loc == -1){ - retval = new String[1]; - retval[0] = reference; + private static String[] separateAreaRefs(String reference) { + // TODO - refactor cell reference parsing logic to one place. + // Current known incarnations: + // FormulaParser.GetName() + // CellReference.separateRefParts() + // AreaReference.separateAreaRefs() (here) + // SheetNameFormatter.format() (inverse) + + + int len = reference.length(); + int delimiterPos = -1; + boolean insideDelimitedName = false; + for(int i=0; i=0) { + throw new IllegalArgumentException("More than one cell delimiter '" + + CELL_DELIMITER + "' appears in area reference '" + reference + "'"); + } + delimiterPos = i; + } + default: + continue; + case SPECIAL_NAME_DELIMITER: + // fall through + } + if(!insideDelimitedName) { + insideDelimitedName = true; + continue; + } + + if(i >= len-1) { + // reference ends with the delimited name. + // Assume names like: "Sheet1!'A1'" are never legal. + throw new IllegalArgumentException("Area reference '" + reference + + "' ends with special name delimiter '" + SPECIAL_NAME_DELIMITER + "'"); + } + if(reference.charAt(i+1) == SPECIAL_NAME_DELIMITER) { + // two consecutive quotes is the escape sequence for a single one + i++; // skip this and keep parsing the special name + } else { + // this is the end of the delimited name + insideDelimitedName = false; + } } - else{ - retval = new String[2]; - int sheetStart = reference.indexOf("!"); - - retval[0] = reference.substring(0, sheetStart+1) + reference.substring(sheetStart + 1,loc); - retval[1] = reference.substring(0, sheetStart+1) + reference.substring(loc+1); + if(delimiterPos < 0) { + return new String[] { reference, }; } - return retval; + + String partA = reference.substring(0, delimiterPos); + String partB = reference.substring(delimiterPos+1); + if(partB.indexOf(SHEET_NAME_DELIMITER) >=0) { + // TODO - are references like "Sheet1!A1:Sheet1:B2" ever valid? + // FormulaParser has code to handle that. + + throw new RuntimeException("Unexpected " + SHEET_NAME_DELIMITER + + " in second cell reference of '" + reference + "'"); + } + + int plingPos = partA.lastIndexOf(SHEET_NAME_DELIMITER); + if(plingPos < 0) { + return new String [] { partA, partB, }; + } + + String sheetName = partA.substring(0, plingPos + 1); // +1 to include delimiter + + return new String [] { partA, sheetName + partB, }; } } \ No newline at end of file diff --git a/src/java/org/apache/poi/hssf/util/CellReference.java b/src/java/org/apache/poi/hssf/util/CellReference.java index def58472a..47d579d94 100644 --- a/src/java/org/apache/poi/hssf/util/CellReference.java +++ b/src/java/org/apache/poi/hssf/util/CellReference.java @@ -15,75 +15,99 @@ limitations under the License. ==================================================================== */ - package org.apache.poi.hssf.util; +import org.apache.poi.hssf.record.formula.SheetNameFormatter; + /** * * @author Avik Sengupta * @author Dennis Doubleday (patch to seperateRowColumns()) */ -public class CellReference { +public final class CellReference { + /** The character ($) that signifies a row or column value is absolute instead of relative */ + private static final char ABSOLUTE_REFERENCE_MARKER = '$'; + /** The character (!) that separates sheet names from cell references */ + private static final char SHEET_NAME_DELIMITER = '!'; + /** The character (') used to quote sheet names when they contain special characters */ + private static final char SPECIAL_NAME_DELIMITER = '\''; + - /** Creates new CellReference */ - private int row; - private int col; - private String sheetName; - private boolean rowAbs; - private boolean colAbs; + private final int _rowIndex; + private final int _colIndex; + private final String _sheetName; + private final boolean _isRowAbs; + private final boolean _isColAbs; + /** + * Create an cell ref from a string representation. Sheet names containing special characters should be + * delimited and escaped as per normal syntax rules for formulas. + */ public CellReference(String cellRef) { String[] parts = separateRefParts(cellRef); - sheetName = parts[0]; - String ref = parts[1]; - if ((ref == null)||("".equals(ref))) - throw new IllegalArgumentException("Invalid Formula cell reference: '"+cellRef+"'"); - if (ref.charAt(0) == '$') { - colAbs=true; - ref=ref.substring(1); + _sheetName = parts[0]; + String colRef = parts[1]; + if (colRef.length() < 1) { + throw new IllegalArgumentException("Invalid Formula cell reference: '"+cellRef+"'"); } - col = convertColStringToNum(ref); - ref=parts[2]; - if ((ref == null)||("".equals(ref))) - throw new IllegalArgumentException("Invalid Formula cell reference: '"+cellRef+"'"); - if (ref.charAt(0) == '$') { - rowAbs=true; - ref=ref.substring(1); + _isColAbs = colRef.charAt(0) == '$'; + if (_isColAbs) { + colRef=colRef.substring(1); } - row = Integer.parseInt(ref)-1; - } - - public CellReference(int pRow, int pCol) { - this(pRow,pCol,false,false); + _colIndex = convertColStringToNum(colRef); + + String rowRef=parts[2]; + if (rowRef.length() < 1) { + throw new IllegalArgumentException("Invalid Formula cell reference: '"+cellRef+"'"); + } + _isRowAbs = rowRef.charAt(0) == '$'; + if (_isRowAbs) { + rowRef=rowRef.substring(1); + } + _rowIndex = Integer.parseInt(rowRef)-1; // -1 to convert 1-based to zero-based } public CellReference(int pRow, int pCol, boolean pAbsRow, boolean pAbsCol) { - row=pRow;col=pCol; - rowAbs = pAbsRow; - colAbs=pAbsCol; - + this(null, pRow, pCol, pAbsRow, pAbsCol); + } + public CellReference(String pSheetName, int pRow, int pCol, boolean pAbsRow, boolean pAbsCol) { + // TODO - "-1" is a special value being temporarily used for whole row and whole column area references. + // so these checks are currently N.Q.R. + if(pRow < -1) { + throw new IllegalArgumentException("row index may not be negative"); + } + if(pCol < -1) { + throw new IllegalArgumentException("column index may not be negative"); + } + _sheetName = pSheetName; + _rowIndex=pRow; + _colIndex=pCol; + _isRowAbs = pAbsRow; + _isColAbs=pAbsCol; } - public int getRow(){return row;} - public short getCol(){return (short) col;} - public boolean isRowAbsolute(){return rowAbs;} - public boolean isColAbsolute(){return colAbs;} - public String getSheetName(){return sheetName;} + public int getRow(){return _rowIndex;} + public short getCol(){return (short) _colIndex;} + public boolean isRowAbsolute(){return _isRowAbs;} + public boolean isColAbsolute(){return _isColAbs;} + /** + * @return possibly null if this is a 2D reference. Special characters are not + * escaped or delimited + */ + public String getSheetName(){ + return _sheetName; + } - protected void setSheetName(String sheetName) { - this.sheetName = sheetName; - } - /** * takes in a column reference portion of a CellRef and converts it from * ALPHA-26 number format to 0-based base 10. */ private int convertColStringToNum(String ref) { - int len = ref.length(); + int lastIx = ref.length()-1; int retval=0; int pos = 0; - for (int k = ref.length()-1; k > -1; k--) { + for (int k = lastIx; k > -1; k--) { char thechar = ref.charAt(k); if ( pos == 0) { retval += (Character.getNumericValue(thechar)-9); @@ -97,42 +121,86 @@ public class CellReference { /** - * Seperates the row from the columns and returns an array. Element in - * position one is the substring containing the columns still in ALPHA-26 - * number format. + * Separates the row from the columns and returns an array of three Strings. The first element + * is the sheet name. Only the first element may be null. The second element in is the column + * name still in ALPHA-26 number format. The third element is the row. */ - private String[] separateRefParts(String reference) { - - // Look for end of sheet name. This will either set - // start to 0 (if no sheet name present) or the - // index after the sheet reference ends. - String retval[] = new String[3]; - - int start = reference.indexOf("!"); - if (start != -1) retval[0] = reference.substring(0, start); - start += 1; + private static String[] separateRefParts(String reference) { + + int plingPos = reference.lastIndexOf(SHEET_NAME_DELIMITER); + String sheetName = parseSheetName(reference, plingPos); + int start = plingPos+1; int length = reference.length(); - char[] chars = reference.toCharArray(); int loc = start; - if (chars[loc]=='$') loc++; - for (; loc < chars.length; loc++) { - if (Character.isDigit(chars[loc]) || chars[loc] == '$') { + // skip initial dollars + if (reference.charAt(loc)==ABSOLUTE_REFERENCE_MARKER) { + loc++; + } + // step over column name chars until first digit (or dollars) for row number. + for (; loc < length; loc++) { + char ch = reference.charAt(loc); + if (Character.isDigit(ch) || ch == ABSOLUTE_REFERENCE_MARKER) { break; } } + return new String[] { + sheetName, + reference.substring(start,loc), + reference.substring(loc), + }; + } - retval[1] = reference.substring(start,loc); - retval[2] = reference.substring(loc); - return retval; + private static String parseSheetName(String reference, int indexOfSheetNameDelimiter) { + if(indexOfSheetNameDelimiter < 0) { + return null; + } + + boolean isQuoted = reference.charAt(0) == SPECIAL_NAME_DELIMITER; + if(!isQuoted) { + return reference.substring(0, indexOfSheetNameDelimiter); + } + int lastQuotePos = indexOfSheetNameDelimiter-1; + if(reference.charAt(lastQuotePos) != SPECIAL_NAME_DELIMITER) { + throw new RuntimeException("Mismatched quotes: (" + reference + ")"); + } + + // TODO - refactor cell reference parsing logic to one place. + // Current known incarnations: + // FormulaParser.GetName() + // CellReference.parseSheetName() (here) + // AreaReference.separateAreaRefs() + // SheetNameFormatter.format() (inverse) + + StringBuffer sb = new StringBuffer(indexOfSheetNameDelimiter); + + for(int i=1; i D */ - private static String convertNumToColString(int col) { + protected static String convertNumToColString(int col) { String retval = null; int mod = col % 26; int div = col / 26; @@ -148,14 +216,46 @@ public class CellReference { return retval; } - + /** + * Example return values: + * + * + * + * + * + *
    ResultComment
    A1Cell reference without sheet
    Sheet1!A1Standard sheet name
    'O''Brien''s Sales'!A1' Sheet name with special characters
    + * @return the text representation of this cell reference as it would appear in a formula. + */ + public String formatAsString() { + StringBuffer sb = new StringBuffer(32); + if(_sheetName != null) { + SheetNameFormatter.appendFormat(sb, _sheetName); + sb.append(SHEET_NAME_DELIMITER); + } + appendCellReference(sb); + return sb.toString(); + } + public String toString() { - StringBuffer retval = new StringBuffer(); - retval.append( (colAbs)?"$":""); - retval.append( convertNumToColString(col)); - retval.append((rowAbs)?"$":""); - retval.append(row+1); + StringBuffer sb = new StringBuffer(64); + sb.append(getClass().getName()).append(" ["); + sb.append(formatAsString()); + sb.append("]"); + return sb.toString(); + } - return retval.toString(); + /** + * Appends cell reference with '$' markers for absolute values as required. + * Sheet name is not included. + */ + /* package */ void appendCellReference(StringBuffer sb) { + if(_isColAbs) { + sb.append(ABSOLUTE_REFERENCE_MARKER); + } + sb.append( convertNumToColString(_colIndex)); + if(_isRowAbs) { + sb.append(ABSOLUTE_REFERENCE_MARKER); + } + sb.append(_rowIndex+1); } } diff --git a/src/java/org/apache/poi/poifs/filesystem/POIFSFileSystem.java b/src/java/org/apache/poi/poifs/filesystem/POIFSFileSystem.java index 771db767b..ef9acfe60 100644 --- a/src/java/org/apache/poi/poifs/filesystem/POIFSFileSystem.java +++ b/src/java/org/apache/poi/poifs/filesystem/POIFSFileSystem.java @@ -19,6 +19,7 @@ package org.apache.poi.poifs.filesystem; +import java.io.ByteArrayInputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; @@ -30,6 +31,8 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.apache.poi.poifs.dev.POIFSViewable; import org.apache.poi.poifs.property.DirectoryProperty; import org.apache.poi.poifs.property.Property; @@ -58,6 +61,33 @@ import org.apache.poi.util.LongField; public class POIFSFileSystem implements POIFSViewable { + private static final Log _logger = LogFactory.getLog(POIFSFileSystem.class); + + + private static final class CloseIgnoringInputStream extends InputStream { + + private final InputStream _is; + public CloseIgnoringInputStream(InputStream is) { + _is = is; + } + public int read() throws IOException { + return _is.read(); + } + public int read(byte[] b, int off, int len) throws IOException { + return _is.read(b, off, len); + } + public void close() { + // do nothing + } + } + + /** + * Convenience method for clients that want to avoid the auto-close behaviour of the constructor. + */ + public static InputStream createNonClosingInputStream(InputStream is) { + return new CloseIgnoringInputStream(is); + } + private PropertyTable _property_table; private List _documents; private DirectoryNode _root; @@ -74,23 +104,52 @@ public class POIFSFileSystem } /** - * Create a POIFSFileSystem from an InputStream + * Create a POIFSFileSystem from an InputStream. Normally the stream is read until + * EOF. The stream is always closed.

    + * + * Some streams are usable after reaching EOF (typically those that return true + * for markSupported()). In the unlikely case that the caller has such a stream + * and needs to use it after this constructor completes, a work around is to wrap the + * stream in order to trap the close() call. A convenience method ( + * createNonClosingInputStream()) has been provided for this purpose: + *

    +     * InputStream wrappedStream = POIFSFileSystem.createNonClosingInputStream(is);
    +     * HSSFWorkbook wb = new HSSFWorkbook(wrappedStream);
    +     * is.reset(); 
    +     * doSomethingElse(is); 
    +     * 
    + * Note also the special case of ByteArrayInputStream for which the close() + * method does nothing. + *
    +     * ByteArrayInputStream bais = ...
    +     * HSSFWorkbook wb = new HSSFWorkbook(bais); // calls bais.close() !
    +     * bais.reset(); // no problem
    +     * doSomethingElse(bais);
    +     * 
    * * @param stream the InputStream from which to read the data * * @exception IOException on errors reading, or on invalid data */ - public POIFSFileSystem(final InputStream stream) + public POIFSFileSystem(InputStream stream) throws IOException { this(); + boolean success = false; // read the header block from the stream - HeaderBlockReader header_block_reader = new HeaderBlockReader(stream); - + HeaderBlockReader header_block_reader; // read the rest of the stream into blocks - RawDataBlockList data_blocks = new RawDataBlockList(stream); + RawDataBlockList data_blocks; + try { + header_block_reader = new HeaderBlockReader(stream); + data_blocks = new RawDataBlockList(stream); + success = true; + } finally { + closeInputStream(stream, success); + } + // set up the block allocation table (necessary for the // data_blocks to be manageable @@ -112,7 +171,32 @@ public class POIFSFileSystem .getSBATStart()), data_blocks, properties.getRoot() .getChildren(), null); } - + /** + * @param stream the stream to be closed + * @param success false if an exception is currently being thrown in the calling method + */ + private void closeInputStream(InputStream stream, boolean success) { + + if(stream.markSupported() && !(stream instanceof ByteArrayInputStream)) { + String msg = "POIFS is closing the supplied input stream of type (" + + stream.getClass().getName() + ") which supports mark/reset. " + + "This will be a problem for the caller if the stream will still be used. " + + "If that is the case the caller should wrap the input stream to avoid this close logic. " + + "This warning is only temporary and will not be present in future versions of POI."; + _logger.warn(msg); + } + try { + stream.close(); + } catch (IOException e) { + if(success) { + throw new RuntimeException(e); + } + // else not success? Try block did not complete normally + // just print stack trace and leave original ex to be thrown + e.printStackTrace(); + } + } + /** * Checks that the supplied InputStream (which MUST * support mark and reset, or be a PushbackInputStream) @@ -123,23 +207,23 @@ public class POIFSFileSystem * @param inp An InputStream which supports either mark/reset, or is a PushbackInputStream */ public static boolean hasPOIFSHeader(InputStream inp) throws IOException { - // We want to peek at the first 8 bytes - inp.mark(8); + // We want to peek at the first 8 bytes + inp.mark(8); - byte[] header = new byte[8]; - IOUtils.readFully(inp, header); + byte[] header = new byte[8]; + IOUtils.readFully(inp, header); LongField signature = new LongField(HeaderBlockConstants._signature_offset, header); // Wind back those 8 bytes if(inp instanceof PushbackInputStream) { - PushbackInputStream pin = (PushbackInputStream)inp; - pin.unread(header); + PushbackInputStream pin = (PushbackInputStream)inp; + pin.unread(header); } else { - inp.reset(); + inp.reset(); } - - // Did it match the signature? - return (signature.get() == HeaderBlockConstants._signature); + + // Did it match the signature? + return (signature.get() == HeaderBlockConstants._signature); } /** diff --git a/src/java/org/apache/poi/poifs/storage/RawDataBlock.java b/src/java/org/apache/poi/poifs/storage/RawDataBlock.java index d55429840..472fd8b8b 100644 --- a/src/java/org/apache/poi/poifs/storage/RawDataBlock.java +++ b/src/java/org/apache/poi/poifs/storage/RawDataBlock.java @@ -21,6 +21,8 @@ package org.apache.poi.poifs.storage; import org.apache.poi.poifs.common.POIFSConstants; import org.apache.poi.util.IOUtils; +import org.apache.poi.util.POILogFactory; +import org.apache.poi.util.POILogger; import java.io.*; @@ -35,6 +37,7 @@ public class RawDataBlock { private byte[] _data; private boolean _eof; + private static POILogger log = POILogFactory.getLogger(RawDataBlock.class); /** * Constructor RawDataBlock @@ -75,9 +78,12 @@ public class RawDataBlock String type = " byte" + ((count == 1) ? ("") : ("s")); - throw new IOException("Unable to read entire block; " + count - + type + " read before EOF; expected " - + blockSize + " bytes"); + log.log(POILogger.ERROR, + "Unable to read entire block; " + count + + type + " read before EOF; expected " + + blockSize + " bytes. Your document" + + " has probably been truncated!" + ); } else { _eof = false; diff --git a/src/java/org/apache/poi/util/LittleEndian.java b/src/java/org/apache/poi/util/LittleEndian.java index b883d71b1..2278649cb 100644 --- a/src/java/org/apache/poi/util/LittleEndian.java +++ b/src/java/org/apache/poi/util/LittleEndian.java @@ -245,6 +245,16 @@ public class LittleEndian putNumber(data, offset, value, SHORT_SIZE); } + /** + * executes:

    + * + * data[offset] = (byte)value; + *

    + * Added for consistency with other put~() methods + */ + public static void putByte(byte[] data, int offset, int value) { + putNumber(data, offset, value, LittleEndianConsts.BYTE_SIZE); + } /** * put a array of shorts into a byte array diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/AddEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/AddEval.java index 5bc0d3e9a..6562263d5 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/AddEval.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/AddEval.java @@ -43,8 +43,6 @@ public class AddEval extends NumericOperationEval { private static final ValueEvalToNumericXlator NUM_XLATOR = new ValueEvalToNumericXlator((short) ( ValueEvalToNumericXlator.BOOL_IS_PARSED - | ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED - | ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED | ValueEvalToNumericXlator.REF_BOOL_IS_PARSED | ValueEvalToNumericXlator.STRING_IS_PARSED | ValueEvalToNumericXlator.REF_STRING_IS_PARSED @@ -59,33 +57,31 @@ public class AddEval extends NumericOperationEval { } - public Eval evaluate(Eval[] operands, int srcRow, short srcCol) { - Eval retval = null; + public Eval evaluate(Eval[] args, int srcRow, short srcCol) { + if(args.length != 2) { + return ErrorEval.VALUE_INVALID; + } + double d = 0; - switch (operands.length) { - default: // will rarely happen. currently the parser itself fails. - retval = ErrorEval.UNKNOWN_ERROR; - break; - case 2: - for (int i = 0, iSize = 2; retval==null && i < iSize; i++) { - ValueEval ve = singleOperandEvaluate(operands[i], srcRow, srcCol); - if (ve instanceof NumericValueEval) { - d += ((NumericValueEval) ve).getNumberValue(); - } - else if (ve instanceof BlankEval) { - // do nothing - } - else { - retval = ErrorEval.VALUE_INVALID; - } - } // end for inside case - } // end switch - - if (retval == null) { - retval = Double.isNaN(d) ? (ValueEval) ErrorEval.VALUE_INVALID : new NumberEval(d); + for (int i = 0; i < 2; i++) { + ValueEval ve = singleOperandEvaluate(args[i], srcRow, srcCol); + if(ve instanceof ErrorEval) { + return ve; + } + if (ve instanceof NumericValueEval) { + d += ((NumericValueEval) ve).getNumberValue(); + } + else if (ve instanceof BlankEval) { + // do nothing + } + else { + return ErrorEval.VALUE_INVALID; + } } - - return retval; + if(Double.isNaN(d) || Double.isInfinite(d)) { + return ErrorEval.NUM_ERROR; + } + return new NumberEval(d); } public int getNumberOfOperands() { diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/Area2DEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/Area2DEval.java index 179698dc8..4b9a64c1c 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/Area2DEval.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/Area2DEval.java @@ -14,10 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on May 8, 2005 - * - */ + package org.apache.poi.hssf.record.formula.eval; import org.apache.poi.hssf.record.formula.AreaPtg; @@ -27,48 +24,60 @@ import org.apache.poi.hssf.record.formula.Ptg; * @author Amol S. Deshmukh < amolweb at ya hoo dot com > * */ -public class Area2DEval implements AreaEval { +public final class Area2DEval implements AreaEval { +// TODO -refactor with Area3DEval + private final AreaPtg _delegate; - private AreaPtg delegate; - - private ValueEval[] values; + private final ValueEval[] _values; public Area2DEval(Ptg ptg, ValueEval[] values) { - this.delegate = (AreaPtg) ptg; - this.values = values; + if(ptg == null) { + throw new IllegalArgumentException("ptg must not be null"); + } + if(values == null) { + throw new IllegalArgumentException("values must not be null"); + } + for(int i=values.length-1; i>=0; i--) { + if(values[i] == null) { + throw new IllegalArgumentException("value array elements must not be null"); + } + } + // TODO - check size of array vs size of AreaPtg + _delegate = (AreaPtg) ptg; + _values = values; } - public short getFirstColumn() { - return delegate.getFirstColumn(); + public int getFirstColumn() { + return _delegate.getFirstColumn(); } public int getFirstRow() { - return delegate.getFirstRow(); + return _delegate.getFirstRow(); } - public short getLastColumn() { - return delegate.getLastColumn(); + public int getLastColumn() { + return _delegate.getLastColumn(); } public int getLastRow() { - return delegate.getLastRow(); + return _delegate.getLastRow(); } public ValueEval[] getValues() { - return values; + return _values; } - public ValueEval getValueAt(int row, short col) { + public ValueEval getValueAt(int row, int col) { ValueEval retval; int index = ((row-getFirstRow())*(getLastColumn()-getFirstColumn()+1))+(col-getFirstColumn()); - if (index <0 || index >= values.length) + if (index <0 || index >= _values.length) retval = ErrorEval.VALUE_INVALID; else - retval = values[index]; + retval = _values[index]; return retval; } - public boolean contains(int row, short col) { + public boolean contains(int row, int col) { return (getFirstRow() <= row) && (getLastRow() >= row) && (getFirstColumn() <= col) && (getLastColumn() >= col); } @@ -82,10 +91,10 @@ public class Area2DEval implements AreaEval { } public boolean isColumn() { - return delegate.getFirstColumn() == delegate.getLastColumn(); + return _delegate.getFirstColumn() == _delegate.getLastColumn(); } public boolean isRow() { - return delegate.getFirstRow() == delegate.getLastRow(); + return _delegate.getFirstRow() == _delegate.getLastRow(); } } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/Area3DEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/Area3DEval.java index d371a0985..2f539142d 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/Area3DEval.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/Area3DEval.java @@ -14,10 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on May 8, 2005 - * - */ + package org.apache.poi.hssf.record.formula.eval; import org.apache.poi.hssf.record.formula.Area3DPtg; @@ -27,48 +24,60 @@ import org.apache.poi.hssf.record.formula.Ptg; * @author Amol S. Deshmukh < amolweb at ya hoo dot com > * */ -public class Area3DEval implements AreaEval { +public final class Area3DEval implements AreaEval { + // TODO -refactor with Area3DEval + private final Area3DPtg _delegate; - private Area3DPtg delegate; - - private ValueEval[] values; + private final ValueEval[] _values; public Area3DEval(Ptg ptg, ValueEval[] values) { - this.values = values; - this.delegate = (Area3DPtg) ptg; + if(ptg == null) { + throw new IllegalArgumentException("ptg must not be null"); + } + if(values == null) { + throw new IllegalArgumentException("values must not be null"); + } + for(int i=values.length-1; i>=0; i--) { + if(values[i] == null) { + throw new IllegalArgumentException("value array elements must not be null"); + } + } + // TODO - check size of array vs size of AreaPtg + _values = values; + _delegate = (Area3DPtg) ptg; } - public short getFirstColumn() { - return delegate.getFirstColumn(); + public int getFirstColumn() { + return _delegate.getFirstColumn(); } public int getFirstRow() { - return delegate.getFirstRow(); + return _delegate.getFirstRow(); } - public short getLastColumn() { - return delegate.getLastColumn(); + public int getLastColumn() { + return (short) _delegate.getLastColumn(); } public int getLastRow() { - return delegate.getLastRow(); + return _delegate.getLastRow(); } public ValueEval[] getValues() { - return values; + return _values; } - public ValueEval getValueAt(int row, short col) { + public ValueEval getValueAt(int row, int col) { ValueEval retval; int index = (row-getFirstRow())*(col-getFirstColumn()); - if (index <0 || index >= values.length) + if (index <0 || index >= _values.length) retval = ErrorEval.VALUE_INVALID; else - retval = values[index]; + retval = _values[index]; return retval; } - public boolean contains(int row, short col) { + public boolean contains(int row, int col) { return (getFirstRow() <= row) && (getLastRow() >= row) && (getFirstColumn() <= col) && (getLastColumn() >= col); } @@ -83,11 +92,14 @@ public class Area3DEval implements AreaEval { public boolean isColumn() { - return delegate.getFirstColumn() == delegate.getLastColumn(); + return _delegate.getFirstColumn() == _delegate.getLastColumn(); } public boolean isRow() { - return delegate.getFirstRow() == delegate.getLastRow(); + return _delegate.getFirstRow() == _delegate.getLastRow(); } + public int getExternSheetIndex() { + return _delegate.getExternSheetIndex(); + } } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/AreaEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/AreaEval.java index f60d63c13..82cc8a9b4 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/AreaEval.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/AreaEval.java @@ -42,13 +42,13 @@ public interface AreaEval extends ValueEval { * returns the 0-based index of the first col in * this area. */ - public short getFirstColumn(); + public int getFirstColumn(); /** * returns the 0-based index of the last col in * this area. */ - public short getLastColumn(); + public int getLastColumn(); /** * returns true if the Area's start and end row indexes @@ -80,7 +80,7 @@ public interface AreaEval extends ValueEval { * @param row * @param col */ - public ValueEval getValueAt(int row, short col); + public ValueEval getValueAt(int row, int col); /** * returns true if the cell at row and col specified @@ -89,7 +89,7 @@ public interface AreaEval extends ValueEval { * @param row * @param col */ - public boolean contains(int row, short col); + public boolean contains(int row, int col); /** * returns true if the specified col is in range diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/BoolEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/BoolEval.java index 6a04068b7..7b625aaa9 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/BoolEval.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/BoolEval.java @@ -34,6 +34,16 @@ public class BoolEval implements NumericValueEval, StringValueEval { public static final BoolEval FALSE = new BoolEval(false); public static final BoolEval TRUE = new BoolEval(true); + + /** + * Convenience method for the following:
    + * (b ? BoolEval.TRUE : BoolEval.FALSE) + * @return a BoolEval instance representing b. + */ + public static final BoolEval valueOf(boolean b) { + // TODO - find / replace all occurrences + return b ? TRUE : FALSE; + } public BoolEval(Ptg ptg) { this.value = ((BoolPtg) ptg).getValue(); @@ -48,10 +58,17 @@ public class BoolEval implements NumericValueEval, StringValueEval { } public double getNumberValue() { - return value ? (short) 1 : (short) 0; + return value ? 1 : 0; } public String getStringValue() { return value ? "TRUE" : "FALSE"; } + public String toString() { + StringBuffer sb = new StringBuffer(64); + sb.append(getClass().getName()).append(" ["); + sb.append(getStringValue()); + sb.append("]"); + return sb.toString(); + } } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ConcatEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ConcatEval.java index 2d8c58ef3..e54cd483f 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ConcatEval.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ConcatEval.java @@ -14,10 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on May 8, 2005 - * - */ + package org.apache.poi.hssf.record.formula.eval; import org.apache.poi.hssf.record.formula.ConcatPtg; @@ -27,7 +24,7 @@ import org.apache.poi.hssf.record.formula.Ptg; * @author Amol S. Deshmukh < amolweb at ya hoo dot com > * */ -public class ConcatEval extends StringOperationEval { +public final class ConcatEval extends StringOperationEval { private ConcatPtg delegate; @@ -35,36 +32,27 @@ public class ConcatEval extends StringOperationEval { this.delegate = (ConcatPtg) ptg; } - public Eval evaluate(Eval[] operands, int srcRow, short srcCol) { - Eval retval = null; - StringBuffer sb = null; - - switch (operands.length) { - default: // paranoid check :) - retval = ErrorEval.UNKNOWN_ERROR; - break; - case 2: - sb = new StringBuffer(); - for (int i = 0, iSize = 2; retval == null && i < iSize; i++) { - - ValueEval ve = singleOperandEvaluate(operands[i], srcRow, srcCol); - if (ve instanceof StringValueEval) { - StringValueEval sve = (StringValueEval) ve; - sb.append(sve.getStringValue()); - } - else if (ve instanceof BlankEval) { - // do nothing - } - else { // must be an error eval - retval = ve; - } + public Eval evaluate(Eval[] args, int srcRow, short srcCol) { + if(args.length != 2) { + return ErrorEval.VALUE_INVALID; + } + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < 2; i++) { + + ValueEval ve = singleOperandEvaluate(args[i], srcRow, srcCol); + if (ve instanceof StringValueEval) { + StringValueEval sve = (StringValueEval) ve; + sb.append(sve.getStringValue()); + } + else if (ve instanceof BlankEval) { + // do nothing + } + else { // must be an error eval + return ve; } } - if (retval == null) { - retval = new StringEval(sb.toString()); - } - return retval; + return new StringEval(sb.toString()); } public int getNumberOfOperands() { diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/DivideEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/DivideEval.java index 6dd3db23d..021168ad7 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/DivideEval.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/DivideEval.java @@ -14,10 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on May 8, 2005 - * - */ + package org.apache.poi.hssf.record.formula.eval; import org.apache.poi.hssf.record.formula.Ptg; @@ -27,15 +24,13 @@ import org.apache.poi.hssf.record.formula.DividePtg; * @author Amol S. Deshmukh < amolweb at ya hoo dot com > * */ -public class DivideEval extends NumericOperationEval { +public final class DivideEval extends NumericOperationEval { private DividePtg delegate; private static final ValueEvalToNumericXlator NUM_XLATOR = new ValueEvalToNumericXlator((short) ( ValueEvalToNumericXlator.BOOL_IS_PARSED - | ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED - | ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED | ValueEvalToNumericXlator.REF_BOOL_IS_PARSED | ValueEvalToNumericXlator.STRING_IS_PARSED | ValueEvalToNumericXlator.REF_STRING_IS_PARSED @@ -49,18 +44,28 @@ public class DivideEval extends NumericOperationEval { return NUM_XLATOR; } - public Eval evaluate(Eval[] operands, int srcRow, short srcCol) { + public Eval evaluate(Eval[] args, int srcRow, short srcCol) { + if(args.length != 2) { + return ErrorEval.VALUE_INVALID; + } Eval retval = null; double d0 = 0; double d1 = 0; - switch (operands.length) { - default: // will rarely happen. currently the parser itself fails. - retval = ErrorEval.UNKNOWN_ERROR; - break; - case 2: - ValueEval ve = singleOperandEvaluate(operands[0], srcRow, srcCol); + ValueEval ve = singleOperandEvaluate(args[0], srcRow, srcCol); + if (ve instanceof NumericValueEval) { + d0 = ((NumericValueEval) ve).getNumberValue(); + } + else if (ve instanceof BlankEval) { + // do nothing + } + else { + retval = ErrorEval.VALUE_INVALID; + } + + if (retval == null) { // no error yet + ve = singleOperandEvaluate(args[1], srcRow, srcCol); if (ve instanceof NumericValueEval) { - d0 = ((NumericValueEval) ve).getNumberValue(); + d1 = ((NumericValueEval) ve).getNumberValue(); } else if (ve instanceof BlankEval) { // do nothing @@ -68,20 +73,7 @@ public class DivideEval extends NumericOperationEval { else { retval = ErrorEval.VALUE_INVALID; } - - if (retval == null) { // no error yet - ve = singleOperandEvaluate(operands[1], srcRow, srcCol); - if (ve instanceof NumericValueEval) { - d1 = ((NumericValueEval) ve).getNumberValue(); - } - else if (ve instanceof BlankEval) { - // do nothing - } - else { - retval = ErrorEval.VALUE_INVALID; - } - } - } // end switch + } if (retval == null) { retval = (d1 == 0) diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ErrorEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ErrorEval.java index 43ef6c512..e8e197d20 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ErrorEval.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ErrorEval.java @@ -14,51 +14,100 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on May 8, 2005 - * - */ + package org.apache.poi.hssf.record.formula.eval; +import org.apache.poi.hssf.usermodel.HSSFErrorConstants; + /** * @author Amol S. Deshmukh < amolweb at ya hoo dot com > - * + * */ -public class ErrorEval implements ValueEval { +public final class ErrorEval implements ValueEval { - private int errorCode; + // convenient access to namespace + private static final HSSFErrorConstants EC = null; + + /** #NULL! - Intersection of two cell ranges is empty */ + public static final ErrorEval NULL_INTERSECTION = new ErrorEval(EC.ERROR_NULL); + /** #DIV/0! - Division by zero */ + public static final ErrorEval DIV_ZERO = new ErrorEval(EC.ERROR_DIV_0); + /** #VALUE! - Wrong type of operand */ + public static final ErrorEval VALUE_INVALID = new ErrorEval(EC.ERROR_VALUE); + /** #REF! - Illegal or deleted cell reference */ + public static final ErrorEval REF_INVALID = new ErrorEval(EC.ERROR_REF); + /** #NAME? - Wrong function or range name */ + public static final ErrorEval NAME_INVALID = new ErrorEval(EC.ERROR_NAME); + /** #NUM! - Value range overflow */ + public static final ErrorEval NUM_ERROR = new ErrorEval(EC.ERROR_NUM); + /** #N/A - Argument or function not available */ + public static final ErrorEval NA = new ErrorEval(EC.ERROR_NA); - public static final ErrorEval NAME_INVALID = new ErrorEval(525); + // POI internal error codes + private static final int CIRCULAR_REF_ERROR_CODE = 0xFFFFFFC4; + private static final int FUNCTION_NOT_IMPLEMENTED_CODE = 0xFFFFFFE2; - public static final ErrorEval VALUE_INVALID = new ErrorEval(519); + public static final ErrorEval FUNCTION_NOT_IMPLEMENTED = new ErrorEval(FUNCTION_NOT_IMPLEMENTED_CODE); + // Note - Excel does not seem to represent this condition with an error code + public static final ErrorEval CIRCULAR_REF_ERROR = new ErrorEval(CIRCULAR_REF_ERROR_CODE); - - // Non std error codes - public static final ErrorEval UNKNOWN_ERROR = new ErrorEval(-20); - public static final ErrorEval FUNCTION_NOT_IMPLEMENTED = new ErrorEval(-30); + /** + * Translates an Excel internal error code into the corresponding POI ErrorEval instance + * @param errorCode + */ + public static ErrorEval valueOf(int errorCode) { + switch(errorCode) { + case HSSFErrorConstants.ERROR_NULL: return NULL_INTERSECTION; + case HSSFErrorConstants.ERROR_DIV_0: return DIV_ZERO; + case HSSFErrorConstants.ERROR_VALUE: return VALUE_INVALID; + case HSSFErrorConstants.ERROR_REF: return REF_INVALID; + case HSSFErrorConstants.ERROR_NAME: return NAME_INVALID; + case HSSFErrorConstants.ERROR_NUM: return NUM_ERROR; + case HSSFErrorConstants.ERROR_NA: return NA; + // non-std errors (conditions modeled as errors by POI) + case CIRCULAR_REF_ERROR_CODE: return CIRCULAR_REF_ERROR; + case FUNCTION_NOT_IMPLEMENTED_CODE: return FUNCTION_NOT_IMPLEMENTED; + } + throw new RuntimeException("Unexpected error code (" + errorCode + ")"); + } - public static final ErrorEval REF_INVALID = new ErrorEval(-40); - - public static final ErrorEval NA = new ErrorEval(-50); - - public static final ErrorEval CIRCULAR_REF_ERROR = new ErrorEval(-60); - - public static final ErrorEval DIV_ZERO = new ErrorEval(-70); - - public static final ErrorEval NUM_ERROR = new ErrorEval(-80); + /** + * Converts error codes to text. Handles non-standard error codes OK. + * For debug/test purposes (and for formatting error messages). + * @return the String representation of the specified Excel error code. + */ + public static String getText(int errorCode) { + if(HSSFErrorConstants.isValidCode(errorCode)) { + return HSSFErrorConstants.getText(errorCode); + } + // It is desirable to make these (arbitrary) strings look clearly different from any other + // value expression that might appear in a formula. In addition these error strings should + // look unlike the standard Excel errors. Hence tilde ('~') was used. + switch(errorCode) { + case CIRCULAR_REF_ERROR_CODE: return "~CIRCULAR~REF~"; + case FUNCTION_NOT_IMPLEMENTED_CODE: return "~FUNCTION~NOT~IMPLEMENTED~"; + } + return "~non~std~err(" + errorCode + ")~"; + } + private int _errorCode; + /** + * @param errorCode an 8-bit value + */ private ErrorEval(int errorCode) { - this.errorCode = errorCode; + _errorCode = errorCode; } public int getErrorCode() { - return errorCode; + return _errorCode; } - - public String getStringValue() { - return "Err:" + Integer.toString(errorCode); + public String toString() { + StringBuffer sb = new StringBuffer(64); + sb.append(getClass().getName()).append(" ["); + sb.append(getText(_errorCode)); + sb.append("]"); + return sb.toString(); } - } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/EvaluationException.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/EvaluationException.java new file mode 100755 index 000000000..7a23901b2 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/EvaluationException.java @@ -0,0 +1,134 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hssf.record.formula.eval; + +/** + * This class is used to simplify error handling logic within operator and function + * implementations. Note - OperationEval.evaluate() and Function.evaluate() + * method signatures do not throw this exception so it cannot propagate outside.

    + * + * Here is an example coded without EvaluationException, to show how it can help: + *

    + * public Eval evaluate(Eval[] args, int srcRow, short srcCol) {
    + *	// ...
    + *	Eval arg0 = args[0];
    + *	if(arg0 instanceof ErrorEval) {
    + *		return arg0;
    + *	}
    + *	if(!(arg0 instanceof AreaEval)) {
    + *		return ErrorEval.VALUE_INVALID;
    + *	}
    + *	double temp = 0;
    + *	AreaEval area = (AreaEval)arg0;
    + *	ValueEval[] values = area.getValues();
    + *	for (int i = 0; i < values.length; i++) {
    + *		ValueEval ve = values[i];
    + *		if(ve instanceof ErrorEval) {
    + *			return ve;
    + *		}
    + *		if(!(ve instanceof NumericValueEval)) {
    + *			return ErrorEval.VALUE_INVALID;
    + *		}
    + *		temp += ((NumericValueEval)ve).getNumberValue();
    + *	}
    + *	// ...
    + * }	 
    + * 
    + * In this example, if any error is encountered while processing the arguments, an error is + * returned immediately. This code is difficult to refactor due to all the points where errors + * are returned.
    + * Using EvaluationException allows the error returning code to be consolidated to one + * place.

    + *

    + * public Eval evaluate(Eval[] args, int srcRow, short srcCol) {
    + *	try {
    + *		// ...
    + *		AreaEval area = getAreaArg(args[0]);
    + *		double temp = sumValues(area.getValues());
    + *		// ...
    + *	} catch (EvaluationException e) {
    + *		return e.getErrorEval();
    + *	}
    + *}
    + *
    + *private static AreaEval getAreaArg(Eval arg0) throws EvaluationException {
    + *	if (arg0 instanceof ErrorEval) {
    + *		throw new EvaluationException((ErrorEval) arg0);
    + *	}
    + *	if (arg0 instanceof AreaEval) {
    + *		return (AreaEval) arg0;
    + *	}
    + *	throw EvaluationException.invalidValue();
    + *}
    + *
    + *private double sumValues(ValueEval[] values) throws EvaluationException {
    + *	double temp = 0;
    + *	for (int i = 0; i < values.length; i++) {
    + *		ValueEval ve = values[i];
    + *		if (ve instanceof ErrorEval) {
    + *			throw new EvaluationException((ErrorEval) ve);
    + *		}
    + *		if (!(ve instanceof NumericValueEval)) {
    + *			throw EvaluationException.invalidValue();
    + *		}
    + *		temp += ((NumericValueEval) ve).getNumberValue();
    + *	}
    + *	return temp;
    + *}
    + * 
    + * It is not mandatory to use EvaluationException, doing so might give the following advantages:
    + * - Methods can more easily be extracted, allowing for re-use.
    + * - Type management (typecasting etc) is simpler because error conditions have been separated from + * intermediate calculation values.
    + * - Fewer local variables are required. Local variables can have stronger types.
    + * - It is easier to mimic common Excel error handling behaviour (exit upon encountering first + * error), because exceptions conveniently propagate up the call stack regardless of execution + * points or the number of levels of nested calls.

    + * + * Note - Only standard evaluation errors are represented by EvaluationException ( + * i.e. conditions expected to be encountered when evaluating arbitrary Excel formulas). Conditions + * that could never occur in an Excel spreadsheet should result in runtime exceptions. Care should + * be taken to not translate any POI internal error into an Excel evaluation error code. + * + * @author Josh Micich + */ +public final class EvaluationException extends Exception { + private final ErrorEval _errorEval; + + public EvaluationException(ErrorEval errorEval) { + _errorEval = errorEval; + } + // some convenience factory methods + + /** #VALUE! - Wrong type of operand */ + public static EvaluationException invalidValue() { + return new EvaluationException(ErrorEval.VALUE_INVALID); + } + /** #REF! - Illegal or deleted cell reference */ + public static EvaluationException invalidRef() { + return new EvaluationException(ErrorEval.REF_INVALID); + } + /** #NUM! - Value range overflow */ + public static EvaluationException numberError() { + return new EvaluationException(ErrorEval.NUM_ERROR); + } + + public ErrorEval getErrorEval() { + return _errorEval; + } +} diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ExternalFunction.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ExternalFunction.java new file mode 100755 index 000000000..b1d81e652 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ExternalFunction.java @@ -0,0 +1,81 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hssf.record.formula.eval; + +import org.apache.poi.hssf.record.formula.functions.FreeRefFunction; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +/** + * + * Common entry point for all external functions (where + * AbstractFunctionPtg.field_2_fnc_index == 255) + * + * @author Josh Micich + */ +final class ExternalFunction implements FreeRefFunction { + + public ValueEval evaluate(Eval[] args, int srcCellRow, short srcCellCol, HSSFWorkbook workbook, HSSFSheet sheet) { + + int nIncomingArgs = args.length; + if(nIncomingArgs < 1) { + throw new RuntimeException("function name argument missing"); + } + + if (!(args[0] instanceof NameEval)) { + throw new RuntimeException("First argument should be a NameEval, but got (" + + args[0].getClass().getName() + ")"); + } + NameEval functionNameEval = (NameEval) args[0]; + + int nOutGoingArgs = nIncomingArgs -1; + Eval[] outGoingArgs = new Eval[nOutGoingArgs]; + System.arraycopy(args, 1, outGoingArgs, 0, nOutGoingArgs); + + FreeRefFunction targetFunc; + try { + targetFunc = findTargetFunction(workbook, functionNameEval); + } catch (EvaluationException e) { + return e.getErrorEval(); + } + + return targetFunc.evaluate(outGoingArgs, srcCellRow, srcCellCol, workbook, sheet); + } + + private FreeRefFunction findTargetFunction(HSSFWorkbook workbook, NameEval functionNameEval) throws EvaluationException { + + int numberOfNames = workbook.getNumberOfNames(); + + int nameIndex = functionNameEval.getIndex(); + if(nameIndex < 0 || nameIndex >= numberOfNames) { + throw new RuntimeException("Bad name index (" + nameIndex + + "). Allowed range is (0.." + (numberOfNames-1) + ")"); + } + + String functionName = workbook.getNameName(nameIndex); + if(false) { + System.out.println("received call to external function index (" + functionName + ")"); + } + // TODO - detect if the NameRecord corresponds to a named range, function, or something undefined + // throw the right errors in these cases + + // TODO find the implementation for the external function e.g. "YEARFRAC" or "ISEVEN" + + throw new EvaluationException(ErrorEval.FUNCTION_NOT_IMPLEMENTED); + } + +} diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/FunctionEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/FunctionEval.java index d1420b2e8..533c604a0 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/FunctionEval.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/FunctionEval.java @@ -20,6 +20,9 @@ */ package org.apache.poi.hssf.record.formula.eval; +import java.util.HashMap; +import java.util.Map; + import org.apache.poi.hssf.record.formula.functions.*; /** @@ -27,12 +30,49 @@ import org.apache.poi.hssf.record.formula.functions.*; * */ public abstract class FunctionEval implements OperationEval { + /** + * Some function IDs that require special treatment + */ + private static final class FunctionID { + /** 78 */ + public static final int OFFSET = 78; + /** 148 */ + public static final int INDIRECT = 148; + /** 255 */ + public static final int EXTERNAL_FUNC = 255; + } + // convenient access to namespace + private static final FunctionID ID = null; + protected static Function[] functions = produceFunctions(); + private static Map freeRefFunctionsByIdMap; + + static { + Map m = new HashMap(); + addMapping(m, ID.OFFSET, new Offset()); + addMapping(m, ID.INDIRECT, new Indirect()); + addMapping(m, ID.EXTERNAL_FUNC, new ExternalFunction()); + freeRefFunctionsByIdMap = m; + } + private static void addMapping(Map m, int offset, FreeRefFunction frf) { + m.put(createFRFKey(offset), frf); + } + private static Integer createFRFKey(int functionIndex) { + return new Integer(functionIndex); + } + + public Function getFunction() { short fidx = getFunctionIndex(); return functions[fidx]; } + public boolean isFreeRefFunction() { + return freeRefFunctionsByIdMap.containsKey(createFRFKey(getFunctionIndex())); + } + public FreeRefFunction getFreeRefFunction() { + return (FreeRefFunction) freeRefFunctionsByIdMap.get(createFRFKey(getFunctionIndex())); + } public abstract short getFunctionIndex(); @@ -115,7 +155,7 @@ public abstract class FunctionEval implements OperationEval { retval[75] = new Areas(); // AREAS retval[76] = new Rows(); // ROWS retval[77] = new Columns(); // COLUMNS - retval[78] = new Offset(); // OFFSET + retval[ID.OFFSET] = null; // Offset.evaluate has a different signature retval[79] = new Absref(); // ABSREF retval[80] = new Relref(); // RELREF retval[81] = new Argument(); // ARGUMENT @@ -185,7 +225,7 @@ public abstract class FunctionEval implements OperationEval { retval[145] = new NotImplementedFunction(); // GETDEF retval[146] = new Reftext(); // REFTEXT retval[147] = new Textref(); // TEXTREF - retval[148] = new Indirect(); // INDIRECT + retval[ID.INDIRECT] = null; // Indirect.evaluate has different signature retval[149] = new NotImplementedFunction(); // REGISTER retval[150] = new Call(); // CALL retval[151] = new NotImplementedFunction(); // ADDBAR @@ -278,7 +318,7 @@ public abstract class FunctionEval implements OperationEval { retval[252] = new Frequency(); // FREQUENCY retval[253] = new NotImplementedFunction(); // ADDTOOLBAR retval[254] = new NotImplementedFunction(); // DELETETOOLBAR - retval[255] = new NotImplementedFunction(); // EXTERNALFLAG + retval[ID.EXTERNAL_FUNC] = null; // ExternalFunction is a FreeREfFunction retval[256] = new NotImplementedFunction(); // RESETTOOLBAR retval[257] = new Evaluate(); // EVALUATE retval[258] = new NotImplementedFunction(); // GETTOOLBAR diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/MultiplyEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/MultiplyEval.java index ecada85d5..22d87b7e4 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/MultiplyEval.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/MultiplyEval.java @@ -14,10 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on May 8, 2005 - * - */ + package org.apache.poi.hssf.record.formula.eval; import org.apache.poi.hssf.record.formula.Ptg; @@ -27,15 +24,13 @@ import org.apache.poi.hssf.record.formula.MultiplyPtg; * @author Amol S. Deshmukh < amolweb at ya hoo dot com > * */ -public class MultiplyEval extends NumericOperationEval { +public final class MultiplyEval extends NumericOperationEval { private MultiplyPtg delegate; private static final ValueEvalToNumericXlator NUM_XLATOR = new ValueEvalToNumericXlator((short) ( ValueEvalToNumericXlator.BOOL_IS_PARSED - | ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED - | ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED | ValueEvalToNumericXlator.REF_BOOL_IS_PARSED | ValueEvalToNumericXlator.STRING_IS_PARSED | ValueEvalToNumericXlator.REF_STRING_IS_PARSED @@ -49,46 +44,39 @@ public class MultiplyEval extends NumericOperationEval { return NUM_XLATOR; } - public Eval evaluate(Eval[] operands, int srcRow, short srcCol) { - Eval retval = null; + public Eval evaluate(Eval[] args, int srcRow, short srcCol) { + if(args.length != 2) { + return ErrorEval.VALUE_INVALID; + } + double d0 = 0; double d1 = 0; - switch (operands.length) { - default: // will rarely happen. currently the parser itself fails. - retval = ErrorEval.UNKNOWN_ERROR; - break; - case 2: - ValueEval ve = singleOperandEvaluate(operands[0], srcRow, srcCol); - if (ve instanceof NumericValueEval) { - d0 = ((NumericValueEval) ve).getNumberValue(); - } - else if (ve instanceof BlankEval) { - // do nothing - } - else { - retval = ErrorEval.VALUE_INVALID; - } - - if (retval == null) { // no error yet - ve = singleOperandEvaluate(operands[1], srcRow, srcCol); - if (ve instanceof NumericValueEval) { - d1 = ((NumericValueEval) ve).getNumberValue(); - } - else if (ve instanceof BlankEval) { - // do nothing - } - else { - retval = ErrorEval.VALUE_INVALID; - } - } - } // end switch - - if (retval == null) { - retval = (Double.isNaN(d0) || Double.isNaN(d1)) - ? (ValueEval) ErrorEval.VALUE_INVALID - : new NumberEval(d0 * d1); + ValueEval ve = singleOperandEvaluate(args[0], srcRow, srcCol); + if (ve instanceof NumericValueEval) { + d0 = ((NumericValueEval) ve).getNumberValue(); } - return retval; + else if (ve instanceof BlankEval) { + // do nothing + } + else { + return ErrorEval.VALUE_INVALID; + } + + ve = singleOperandEvaluate(args[1], srcRow, srcCol); + if (ve instanceof NumericValueEval) { + d1 = ((NumericValueEval) ve).getNumberValue(); + } + else if (ve instanceof BlankEval) { + // do nothing + } + else { + return ErrorEval.VALUE_INVALID; + } + + if (Double.isNaN(d0) || Double.isNaN(d1)) { + return ErrorEval.NUM_ERROR; + } + return new NumberEval(d0 * d1); } public int getNumberOfOperands() { diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/NameEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/NameEval.java new file mode 100755 index 000000000..682394b3c --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/NameEval.java @@ -0,0 +1,48 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hssf.record.formula.eval; + +/** + * @author Josh Micich + */ +public final class NameEval implements Eval { + + private final int _index; + + /** + * @param index zero based index to a defined name record + */ + public NameEval(int index) { + _index = index; + } + + /** + * @return zero based index to a defined name record + */ + public int getIndex() { + return _index; + } + + public String toString() { + StringBuffer sb = new StringBuffer(64); + sb.append(getClass().getName()).append(" ["); + sb.append(_index); + sb.append("]"); + return sb.toString(); + } +} diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/OperandResolver.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/OperandResolver.java new file mode 100755 index 000000000..be1cda5f8 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/OperandResolver.java @@ -0,0 +1,277 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hssf.record.formula.eval; + +/** + * Provides functionality for evaluating arguments to functions and operators. + * + * @author Josh Micich + */ +public final class OperandResolver { + + private OperandResolver() { + // no instances of this class + } + + /** + * Retrieves a single value from a variety of different argument types according to standard + * Excel rules. Does not perform any type conversion. + * @param arg the evaluated argument as passed to the function or operator. + * @param srcCellRow used when arg is a single column AreaRef + * @param srcCellCol used when arg is a single row AreaRef + * @return a NumberEval, StringEval, BoolEval or BlankEval. + * Never null or ErrorEval. + * @throws EvaluationException(#VALUE!) if srcCellRow or srcCellCol do not properly index into + * an AreaEval. If the actual value retrieved is an ErrorEval, a corresponding + * EvaluationException is thrown. + */ + public static ValueEval getSingleValue(Eval arg, int srcCellRow, short srcCellCol) + throws EvaluationException { + Eval result; + if (arg instanceof RefEval) { + result = ((RefEval) arg).getInnerValueEval(); + } else if (arg instanceof AreaEval) { + result = chooseSingleElementFromArea((AreaEval) arg, srcCellRow, srcCellCol); + } else { + result = arg; + } + if (result instanceof ErrorEval) { + throw new EvaluationException((ErrorEval) result); + } + if (result instanceof ValueEval) { + return (ValueEval) result; + } + throw new RuntimeException("Unexpected eval type (" + result.getClass().getName() + ")"); + } + + /** + * Implements (some perhaps not well known) Excel functionality to select a single cell from an + * area depending on the coordinates of the calling cell. Here is an example demonstrating + * both selection from a single row area and a single column area in the same formula. + * + * + * + * + * + * + * + *
      A  B  C  D 
    1152025 
    2   200
    3   300
    3   400
    + * + * If the formula "=1000+A1:B1+D2:D3" is put into the 9 cells from A2 to C4, the spreadsheet + * will look like this: + * + * + * + * + * + * + * + *
      A  B  C  D 
    1152025 
    212151220#VALUE!200
    313151320#VALUE!300
    4#VALUE!#VALUE!#VALUE!400
    + * + * Note that the row area (A1:B1) does not include column C and the column area (D2:D3) does + * not include row 4, so the values in C1(=25) and D4(=400) are not accessible to the formula + * as written, but in the 4 cells A2:B3, the row and column selection works ok.

    + * + * The same concept is extended to references across sheets, such that even multi-row, + * multi-column areas can be useful.

    + * + * Of course with carefully (or carelessly) chosen parameters, cyclic references can occur and + * hence this method can throw a 'circular reference' EvaluationException. Note that + * this method does not attempt to detect cycles. Every cell in the specified Area ae + * has already been evaluated prior to this method call. Any cell (or cells) part of + * ae that would incur a cyclic reference error if selected by this method, will + * already have the value ErrorEval.CIRCULAR_REF_ERROR upon entry to this method. It + * is assumed logic exists elsewhere to produce this behaviour. + * + * @return whatever the selected cell's evaluated value is. Never null. Never + * ErrorEval. + * @throws EvaluationException if there is a problem with indexing into the area, or if the + * evaluated cell has an error. + */ + public static ValueEval chooseSingleElementFromArea(AreaEval ae, + int srcCellRow, short srcCellCol) throws EvaluationException { + ValueEval result = chooseSingleElementFromAreaInternal(ae, srcCellRow, srcCellCol); + if(result == null) { + // This seems to be required because AreaEval.values() array may contain nulls. + // perhaps that should not be allowed. + result = BlankEval.INSTANCE; + } + if (result instanceof ErrorEval) { + throw new EvaluationException((ErrorEval) result); + + } + return result; + } + + /** + * @return possibly ErrorEval, and null + */ + private static ValueEval chooseSingleElementFromAreaInternal(AreaEval ae, + int srcCellRow, short srcCellCol) throws EvaluationException { + + if(false) { + // this is too simplistic + if(ae.containsRow(srcCellRow) && ae.containsColumn(srcCellCol)) { + throw new EvaluationException(ErrorEval.CIRCULAR_REF_ERROR); + } + /* + Circular references are not dealt with directly here, but it is worth noting some issues. + + ANY one of the return statements in this method could return a cell that is identical + to the one immediately being evaluated. The evaluating cell is identified by srcCellRow, + srcCellRow AND sheet. The sheet is not available in any nearby calling method, so that's + one reason why circular references are not easy to detect here. (The sheet of the returned + cell can be obtained from ae if it is an Area3DEval.) + + Another reason there's little value in attempting to detect circular references here is + that only direct circular references could be detected. If the cycle involved two or more + cells this method could not detect it. + + Logic to detect evaluation cycles of all kinds has been coded in EvaluationCycleDetector + (and HSSFFormulaEvaluator). + */ + } + + if (ae.isColumn()) { + if(ae.isRow()) { + return ae.getValues()[0]; + } + if(!ae.containsRow(srcCellRow)) { + throw EvaluationException.invalidValue(); + } + return ae.getValueAt(srcCellRow, ae.getFirstColumn()); + } + if(!ae.isRow()) { + // multi-column, multi-row area + if(ae.containsRow(srcCellRow) && ae.containsColumn(srcCellCol)) { + return ae.getValueAt(ae.getFirstRow(), ae.getFirstColumn()); + } + throw EvaluationException.invalidValue(); + } + if(!ae.containsColumn(srcCellCol)) { + throw EvaluationException.invalidValue(); + } + return ae.getValueAt(ae.getFirstRow(), srcCellCol); + } + + /** + * Applies some conversion rules if the supplied value is not already an integer.
    + * Value is first coerced to a double ( See coerceValueToDouble() ).

    + * + * Excel typically converts doubles to integers by truncating toward negative infinity.
    + * The equivalent java code is:
    + *   return (int)Math.floor(d);
    + * not:
    + *   return (int)d; // wrong - rounds toward zero + * + */ + public static int coerceValueToInt(ValueEval ev) throws EvaluationException { + double d = coerceValueToDouble(ev); + // Note - the standard java type conversion from double to int truncates toward zero. + // but Math.floor() truncates toward negative infinity + return (int)Math.floor(d); + } + + /** + * Applies some conversion rules if the supplied value is not already a number. + * Note - BlankEval is not supported and must be handled by the caller. + * @param ev must be a NumberEval, StringEval or BoolEval + * @return actual, parsed or interpreted double value (respectively). + * @throws EvaluationException(#VALUE!) only if a StringEval is supplied and cannot be parsed + * as a double (See parseDouble() for allowable formats). + * @throws RuntimeException if the supplied parameter is not NumberEval, + * StringEval or BoolEval + */ + public static double coerceValueToDouble(ValueEval ev) throws EvaluationException { + + if (ev instanceof NumericValueEval) { + // this also handles booleans + return ((NumericValueEval)ev).getNumberValue(); + } + if (ev instanceof StringEval) { + Double dd = parseDouble(((StringEval) ev).getStringValue()); + if (dd == null) { + throw EvaluationException.invalidValue(); + } + return dd.doubleValue(); + } + throw new RuntimeException("Unexpected arg eval type (" + ev.getClass().getName() + ")"); + } + + /** + * Converts a string to a double using standard rules that Excel would use.
    + * Tolerates currency prefixes, commas, leading and trailing spaces.

    + * + * Some examples:
    + * " 123 " -> 123.0
    + * ".123" -> 0.123
    + * These not supported yet:
    + * " $ 1,000.00 " -> 1000.0
    + * "$1.25E4" -> 12500.0
    + * "5**2" -> 500
    + * "250%" -> 2.5
    + * + * @param text + * @return null if the specified text cannot be parsed as a number + */ + public static Double parseDouble(String pText) { + String text = pText.trim(); + if(text.length() < 1) { + return null; + } + boolean isPositive = true; + if(text.charAt(0) == '-') { + isPositive = false; + text= text.substring(1).trim(); + } + + if(!Character.isDigit(text.charAt(0))) { + // avoid using NumberFormatException to tell when string is not a number + return null; + } + // TODO - support notation like '1E3' (==1000) + + double val; + try { + val = Double.parseDouble(text); + } catch (NumberFormatException e) { + return null; + } + return new Double(isPositive ? +val : -val); + } + + /** + * @param ve must be a NumberEval, StringEval, BoolEval, or BlankEval + * @return the converted string value. never null + */ + public static String coerceValueToString(ValueEval ve) { + if (ve instanceof StringValueEval) { + StringValueEval sve = (StringValueEval) ve; + return sve.getStringValue(); + } + if (ve instanceof NumberEval) { + NumberEval neval = (NumberEval) ve; + return neval.getStringValue(); + } + + if (ve instanceof BlankEval) { + return ""; + } + throw new IllegalArgumentException("Unexpected eval class (" + ve.getClass().getName() + ")"); + } +} diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/PowerEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/PowerEval.java index 437c24e40..651c5d2aa 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/PowerEval.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/PowerEval.java @@ -14,10 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on May 8, 2005 - * - */ + package org.apache.poi.hssf.record.formula.eval; import org.apache.poi.hssf.record.formula.Ptg; @@ -27,15 +24,13 @@ import org.apache.poi.hssf.record.formula.PowerPtg; * @author Amol S. Deshmukh < amolweb at ya hoo dot com > * */ -public class PowerEval extends NumericOperationEval { +public final class PowerEval extends NumericOperationEval { private PowerPtg delegate; private static final ValueEvalToNumericXlator NUM_XLATOR = new ValueEvalToNumericXlator((short) ( ValueEvalToNumericXlator.BOOL_IS_PARSED - | ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED - | ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED | ValueEvalToNumericXlator.REF_BOOL_IS_PARSED | ValueEvalToNumericXlator.STRING_IS_PARSED | ValueEvalToNumericXlator.REF_STRING_IS_PARSED @@ -49,48 +44,40 @@ public class PowerEval extends NumericOperationEval { return NUM_XLATOR; } - public Eval evaluate(Eval[] operands, int srcRow, short srcCol) { - Eval retval = null; + public Eval evaluate(Eval[] args, int srcRow, short srcCol) { + if(args.length != 2) { + return ErrorEval.VALUE_INVALID; + } double d0 = 0; double d1 = 0; - switch (operands.length) { - default: // will rarely happen. currently the parser itself fails. - retval = ErrorEval.UNKNOWN_ERROR; - break; - case 2: - ValueEval ve = singleOperandEvaluate(operands[0], srcRow, srcCol); - if (ve instanceof NumericValueEval) { - d0 = ((NumericValueEval) ve).getNumberValue(); - } - else if (ve instanceof BlankEval) { - // do nothing - } - else { - retval = ErrorEval.VALUE_INVALID; - } - - if (retval == null) { // no error yet - ve = singleOperandEvaluate(operands[1], srcRow, srcCol); - if (ve instanceof NumericValueEval) { - d1 = ((NumericValueEval) ve).getNumberValue(); - } - else if (ve instanceof BlankEval) { - // do nothing - } - else { - retval = ErrorEval.VALUE_INVALID; - } - } - } // end switch - - if (retval == null) { - double p = Math.pow(d0, d1); - retval = (Double.isNaN(p)) - ? (ValueEval) ErrorEval.VALUE_INVALID - : new NumberEval(p); + ValueEval ve = singleOperandEvaluate(args[0], srcRow, srcCol); + if (ve instanceof NumericValueEval) { + d0 = ((NumericValueEval) ve).getNumberValue(); } - return retval; + else if (ve instanceof BlankEval) { + // do nothing + } + else { + return ErrorEval.VALUE_INVALID; + } + + ve = singleOperandEvaluate(args[1], srcRow, srcCol); + if (ve instanceof NumericValueEval) { + d1 = ((NumericValueEval) ve).getNumberValue(); + } + else if (ve instanceof BlankEval) { + // do nothing + } + else { + return ErrorEval.VALUE_INVALID; + } + + double p = Math.pow(d0, d1); + if (Double.isNaN(p)) { + return ErrorEval.VALUE_INVALID; + } + return new NumberEval(p); } public int getNumberOfOperands() { diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/Ref2DEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/Ref2DEval.java index 7b24cb062..898d7a861 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/Ref2DEval.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/Ref2DEval.java @@ -14,47 +14,37 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on May 9, 2005 - * - */ + package org.apache.poi.hssf.record.formula.eval; -import org.apache.poi.hssf.record.formula.Ptg; import org.apache.poi.hssf.record.formula.ReferencePtg; /** * @author adeshmukh * */ -public class Ref2DEval implements RefEval { +public final class Ref2DEval implements RefEval { - private ValueEval value; - - private ReferencePtg delegate; + private final ValueEval value; + private final ReferencePtg delegate; - private boolean evaluated; - - public Ref2DEval(Ptg ptg, ValueEval value, boolean evaluated) { - this.value = value; - this.delegate = (ReferencePtg) ptg; - this.evaluated = evaluated; + public Ref2DEval(ReferencePtg ptg, ValueEval ve) { + if(ve == null) { + throw new IllegalArgumentException("ve must not be null"); + } + if(false && ptg == null) { // TODO - fix dodgy code in MultiOperandNumericFunction + throw new IllegalArgumentException("ptg must not be null"); + } + value = ve; + delegate = ptg; } - public ValueEval getInnerValueEval() { return value; } - - public short getRow() { + public int getRow() { return delegate.getRow(); } - - public short getColumn() { + public int getColumn() { return delegate.getColumn(); } - - public boolean isEvaluated() { - return evaluated; - } - } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/Ref3DEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/Ref3DEval.java index acedbe766..622d68632 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/Ref3DEval.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/Ref3DEval.java @@ -14,47 +14,40 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on May 9, 2005 - * - */ + package org.apache.poi.hssf.record.formula.eval; -import org.apache.poi.hssf.record.formula.Ptg; import org.apache.poi.hssf.record.formula.Ref3DPtg; /** * @author Amol S. Deshmukh * */ -public class Ref3DEval implements RefEval { +public final class Ref3DEval implements RefEval { - private ValueEval value; + private final ValueEval value; + private final Ref3DPtg delegate; - private Ref3DPtg delegate; - - private boolean evaluated; - - public Ref3DEval(Ptg ptg, ValueEval value, boolean evaluated) { - this.value = value; - this.delegate = (Ref3DPtg) ptg; - this.evaluated = evaluated; + public Ref3DEval(Ref3DPtg ptg, ValueEval ve) { + if(ve == null) { + throw new IllegalArgumentException("ve must not be null"); + } + if(ptg == null) { + throw new IllegalArgumentException("ptg must not be null"); + } + value = ve; + delegate = ptg; } - public ValueEval getInnerValueEval() { return value; } - - public short getRow() { + public int getRow() { return delegate.getRow(); } - - public short getColumn() { + public int getColumn() { return delegate.getColumn(); } - - public boolean isEvaluated() { - return evaluated; + public int getExternSheetIndex() { + return delegate.getExternSheetIndex(); } - } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/RefEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/RefEval.java index bb72adc4a..e462586d7 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/RefEval.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/RefEval.java @@ -14,11 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on May 9, 2005 - * - * - */ + package org.apache.poi.hssf.record.formula.eval; /** @@ -44,26 +40,12 @@ public interface RefEval extends ValueEval { public ValueEval getInnerValueEval(); /** - * returns the column index. + * returns the zero based column index. */ - public short getColumn(); + public int getColumn(); /** - * returns the row index. + * returns the zero based row index. */ - public short getRow(); - - /** - * returns true if this RefEval contains an - * evaluated value instead of a direct value. - * eg. say cell A1 has the value: ="test" - * Then the RefEval representing A1 will return - * isEvaluated() equal to false. On the other - * hand, say cell A1 has the value: =B1 and - * B1 has the value "test", then the RefEval - * representing A1 will return isEvaluated() - * equal to true. - */ - public boolean isEvaluated(); - + public int getRow(); } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/StringEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/StringEval.java index 01af4e843..27a9c6a62 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/StringEval.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/StringEval.java @@ -14,10 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on May 8, 2005 - * - */ + package org.apache.poi.hssf.record.formula.eval; import org.apache.poi.hssf.record.formula.Ptg; @@ -27,21 +24,31 @@ import org.apache.poi.hssf.record.formula.StringPtg; * @author Amol S. Deshmukh < amolweb at ya hoo dot com > * */ -public class StringEval implements StringValueEval { +public final class StringEval implements StringValueEval { public static final StringEval EMPTY_INSTANCE = new StringEval(""); - private String value; + private final String value; public StringEval(Ptg ptg) { - this.value = ((StringPtg) ptg).getValue(); + this(((StringPtg) ptg).getValue()); } public StringEval(String value) { + if(value == null) { + throw new IllegalArgumentException("value must not be null"); + } this.value = value; } public String getStringValue() { return value; } + public String toString() { + StringBuffer sb = new StringBuffer(64); + sb.append(getClass().getName()).append(" ["); + sb.append(value); + sb.append("]"); + return sb.toString(); + } } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/StringValueEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/StringValueEval.java index b692f01ea..46c12236b 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/StringValueEval.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/StringValueEval.java @@ -14,10 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on May 8, 2005 - * - */ + package org.apache.poi.hssf.record.formula.eval; /** @@ -26,5 +23,8 @@ package org.apache.poi.hssf.record.formula.eval; */ public interface StringValueEval extends ValueEval { - public String getStringValue(); + /** + * @return never null, possibly empty string. + */ + String getStringValue(); } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/SubtractEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/SubtractEval.java index 4bd77029f..85a384529 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/SubtractEval.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/SubtractEval.java @@ -14,10 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on May 8, 2005 - * - */ + package org.apache.poi.hssf.record.formula.eval; import org.apache.poi.hssf.record.formula.Ptg; @@ -27,15 +24,13 @@ import org.apache.poi.hssf.record.formula.SubtractPtg; * @author Amol S. Deshmukh < amolweb at ya hoo dot com > * */ -public class SubtractEval extends NumericOperationEval { +public final class SubtractEval extends NumericOperationEval { private SubtractPtg delegate; private static final ValueEvalToNumericXlator NUM_XLATOR = new ValueEvalToNumericXlator((short) ( ValueEvalToNumericXlator.BOOL_IS_PARSED - | ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED - | ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED | ValueEvalToNumericXlator.REF_BOOL_IS_PARSED | ValueEvalToNumericXlator.STRING_IS_PARSED | ValueEvalToNumericXlator.REF_STRING_IS_PARSED @@ -49,18 +44,28 @@ public class SubtractEval extends NumericOperationEval { return NUM_XLATOR; } - public Eval evaluate(Eval[] operands, int srcRow, short srcCol) { + public Eval evaluate(Eval[] args, int srcRow, short srcCol) { + if(args.length != 2) { + return ErrorEval.VALUE_INVALID; + } Eval retval = null; double d0 = 0; double d1 = 0; - switch (operands.length) { - default: // will rarely happen. currently the parser itself fails. - retval = ErrorEval.UNKNOWN_ERROR; - break; - case 2: - ValueEval ve = singleOperandEvaluate(operands[0], srcRow, srcCol); + ValueEval ve = singleOperandEvaluate(args[0], srcRow, srcCol); + if (ve instanceof NumericValueEval) { + d0 = ((NumericValueEval) ve).getNumberValue(); + } + else if (ve instanceof BlankEval) { + // do nothing + } + else { + retval = ErrorEval.VALUE_INVALID; + } + + if (retval == null) { // no error yet + ve = singleOperandEvaluate(args[1], srcRow, srcCol); if (ve instanceof NumericValueEval) { - d0 = ((NumericValueEval) ve).getNumberValue(); + d1 = ((NumericValueEval) ve).getNumberValue(); } else if (ve instanceof BlankEval) { // do nothing @@ -68,21 +73,8 @@ public class SubtractEval extends NumericOperationEval { else { retval = ErrorEval.VALUE_INVALID; } - - if (retval == null) { // no error yet - ve = singleOperandEvaluate(operands[1], srcRow, srcCol); - if (ve instanceof NumericValueEval) { - d1 = ((NumericValueEval) ve).getNumberValue(); - } - else if (ve instanceof BlankEval) { - // do nothing - } - else { - retval = ErrorEval.VALUE_INVALID; - } - } - } // end switch - + } + if (retval == null) { retval = (Double.isNaN(d0) || Double.isNaN(d1)) ? (ValueEval) ErrorEval.VALUE_INVALID diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/UnaryMinusEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/UnaryMinusEval.java index b4975eefc..ef6f533ea 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/UnaryMinusEval.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/UnaryMinusEval.java @@ -14,10 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on May 8, 2005 - * - */ + package org.apache.poi.hssf.record.formula.eval; import org.apache.poi.hssf.record.formula.Ptg; @@ -27,14 +24,12 @@ import org.apache.poi.hssf.record.formula.UnaryMinusPtg; * @author Amol S. Deshmukh < amolweb at ya hoo dot com > * */ -public class UnaryMinusEval extends NumericOperationEval { +public final class UnaryMinusEval extends NumericOperationEval { private UnaryMinusPtg delegate; private static final ValueEvalToNumericXlator NUM_XLATOR = new ValueEvalToNumericXlator((short) ( ValueEvalToNumericXlator.BOOL_IS_PARSED - | ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED - | ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED | ValueEvalToNumericXlator.REF_BOOL_IS_PARSED | ValueEvalToNumericXlator.STRING_IS_PARSED | ValueEvalToNumericXlator.REF_STRING_IS_PARSED @@ -49,32 +44,24 @@ public class UnaryMinusEval extends NumericOperationEval { return NUM_XLATOR; } - public Eval evaluate(Eval[] operands, int srcRow, short srcCol) { - ValueEval retval = null; + public Eval evaluate(Eval[] args, int srcRow, short srcCol) { + if(args.length != 1) { + return ErrorEval.VALUE_INVALID; + } double d = 0; - switch (operands.length) { - default: - retval = ErrorEval.UNKNOWN_ERROR; - break; - case 1: - ValueEval ve = singleOperandEvaluate(operands[0], srcRow, srcCol); - if (ve instanceof NumericValueEval) { - d = ((NumericValueEval) ve).getNumberValue(); - } - else if (ve instanceof BlankEval) { - // do nothing - } - else if (ve instanceof ErrorEval) { - retval = ve; - } + ValueEval ve = singleOperandEvaluate(args[0], srcRow, srcCol); + if (ve instanceof NumericValueEval) { + d = ((NumericValueEval) ve).getNumberValue(); + } + else if (ve instanceof BlankEval) { + // do nothing + } + else if (ve instanceof ErrorEval) { + return ve; } - if (retval == null) { - retval = new NumberEval(-d); - } - - return retval; + return new NumberEval(-d); } public int getNumberOfOperands() { diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/UnaryPlusEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/UnaryPlusEval.java index 847aa56fa..edcc7bee7 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/UnaryPlusEval.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/UnaryPlusEval.java @@ -14,10 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on May 8, 2005 - * - */ + package org.apache.poi.hssf.record.formula.eval; import org.apache.poi.hssf.record.formula.Ptg; @@ -27,111 +24,38 @@ import org.apache.poi.hssf.record.formula.UnaryPlusPtg; * @author Amol S. Deshmukh < amolweb at ya hoo dot com > * */ -public class UnaryPlusEval implements OperationEval /*extends NumericOperationEval*/ { +public final class UnaryPlusEval implements OperationEval { private UnaryPlusPtg delegate; - /* - * COMMENT FOR COMMENTED CODE IN THIS FILE - * - * In excel the programmer seems to not have cared to - * think about how strings were handled in other numeric - * operations when he/she was implementing this operation :P - * - * Here's what I mean: - * - * Q. If the formula -"hello" evaluates to #VALUE! in excel, what should - * the formula +"hello" evaluate to? - * - * A. +"hello" evaluates to "hello" (what the...?) - * + /** + * called by reflection */ - - -// private static final ValueEvalToNumericXlator NUM_XLATOR = -// new ValueEvalToNumericXlator((short) -// ( ValueEvalToNumericXlator.BOOL_IS_PARSED -// | ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED -// | ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED -// | ValueEvalToNumericXlator.REF_BOOL_IS_PARSED -// | ValueEvalToNumericXlator.STRING_IS_PARSED -// )); - - public UnaryPlusEval(Ptg ptg) { this.delegate = (UnaryPlusPtg) ptg; } - -// protected ValueEvalToNumericXlator getXlator() { -// return NUM_XLATOR; -// } - public Eval evaluate(Eval[] operands, int srcRow, short srcCol) { - ValueEval retval = null; - - switch (operands.length) { - default: - retval = ErrorEval.UNKNOWN_ERROR; - break; - case 1: - -// ValueEval ve = singleOperandEvaluate(operands[0], srcRow, srcCol); -// if (ve instanceof NumericValueEval) { -// d = ((NumericValueEval) ve).getNumberValue(); -// } -// else if (ve instanceof BlankEval) { -// // do nothing -// } -// else if (ve instanceof ErrorEval) { -// retval = ve; -// } - if (operands[0] instanceof RefEval) { - RefEval re = (RefEval) operands[0]; - retval = re.getInnerValueEval(); - } - else if (operands[0] instanceof AreaEval) { - AreaEval ae = (AreaEval) operands[0]; - if (ae.contains(srcRow, srcCol)) { // circular ref! - retval = ErrorEval.CIRCULAR_REF_ERROR; - } - else if (ae.isRow()) { - if (ae.containsColumn(srcCol)) { - ValueEval ve = ae.getValueAt(ae.getFirstRow(), srcCol); - if (ve instanceof RefEval) { - ve = ((RefEval) ve).getInnerValueEval(); - } - retval = ve; - } - else { - retval = ErrorEval.VALUE_INVALID; - } - } - else if (ae.isColumn()) { - if (ae.containsRow(srcRow)) { - ValueEval ve = ae.getValueAt(ae.getFirstRow(), srcCol); - if (ve instanceof RefEval) { - ve = ((RefEval) ve).getInnerValueEval(); - } - retval = ve; - } - else { - retval = ErrorEval.VALUE_INVALID; - } - } - else { - retval = ErrorEval.VALUE_INVALID; - } - } - else { - retval = (ValueEval) operands[0]; - } - } - - if (retval instanceof BlankEval) { - retval = new NumberEval(0); - } - - return retval; + public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) { + if(args.length != 1) { + return ErrorEval.VALUE_INVALID; + } + double d; + try { + ValueEval ve = OperandResolver.getSingleValue(args[0], srcCellRow, srcCellCol); + if(ve instanceof BlankEval) { + return NumberEval.ZERO; + } + if(ve instanceof StringEval) { + // Note - asymmetric with UnaryMinus + // -"hello" evaluates to #VALUE! + // but +"hello" evaluates to "hello" + return ve; + } + d = OperandResolver.coerceValueToDouble(ve); + } catch (EvaluationException e) { + return e.getErrorEval(); + } + return new NumberEval(+d); } public int getNumberOfOperands() { @@ -141,5 +65,4 @@ public class UnaryPlusEval implements OperationEval /*extends NumericOperationEv public int getType() { return delegate.getType(); } - } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ValueEvalToNumericXlator.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ValueEvalToNumericXlator.java index 5ffa2faee..1abcf34d2 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ValueEvalToNumericXlator.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ValueEvalToNumericXlator.java @@ -24,7 +24,7 @@ package org.apache.poi.hssf.record.formula.eval; * @author Amol S. Deshmukh < amolweb at ya hoo dot com > * */ -public class ValueEvalToNumericXlator { +public final class ValueEvalToNumericXlator { public static final int STRING_IS_PARSED = 0x0001; public static final int BOOL_IS_PARSED = 0x0002; @@ -34,26 +34,18 @@ public class ValueEvalToNumericXlator { public static final int REF_BOOL_IS_PARSED = 0x0010; public static final int REF_BLANK_IS_PARSED = 0x0020; - public static final int EVALUATED_REF_STRING_IS_PARSED = 0x0040; - public static final int EVALUATED_REF_BOOL_IS_PARSED = 0x0080; - public static final int EVALUATED_REF_BLANK_IS_PARSED = 0x0100; - - public static final int STRING_TO_BOOL_IS_PARSED = 0x0200; - public static final int REF_STRING_TO_BOOL_IS_PARSED = 0x0400; - public static final int STRING_IS_INVALID_VALUE = 0x0800; - public static final int REF_STRING_IS_INVALID_VALUE = 0x1000; - -// public static final int BOOL_IS_BLANK = 0x2000; -// public static final int REF_BOOL_IS_BLANK = 0x4000; -// public static final int STRING_IS_BLANK = 0x8000; -// public static final int REF_STRING_IS_BLANK = 0x10000; private final int flags; public ValueEvalToNumericXlator(int flags) { - this.flags = flags; + + if (false) { // uncomment to see who is using this class + System.err.println(new Throwable().getStackTrace()[1].getClassName() + "\t0x" + + Integer.toHexString(flags).toUpperCase()); + } + this.flags = flags; } /** @@ -71,7 +63,7 @@ public class ValueEvalToNumericXlator { // most common case - least worries :) else if (eval instanceof NumberEval) { - retval = (NumberEval) eval; + retval = eval; } // booleval @@ -125,50 +117,33 @@ public class ValueEvalToNumericXlator { * @param eval */ private ValueEval xlateRefEval(RefEval reval) { - ValueEval retval = null; - ValueEval eval = (ValueEval) reval.getInnerValueEval(); + ValueEval eval = reval.getInnerValueEval(); // most common case - least worries :) if (eval instanceof NumberEval) { - retval = (NumberEval) eval; + return eval; } - // booleval - else if (eval instanceof BoolEval) { - retval = ((flags & REF_BOOL_IS_PARSED) > 0) + if (eval instanceof BoolEval) { + return ((flags & REF_BOOL_IS_PARSED) > 0) ? (ValueEval) eval : BlankEval.INSTANCE; } - // stringeval - else if (eval instanceof StringEval) { - retval = xlateRefStringEval((StringEval) eval); + if (eval instanceof StringEval) { + return xlateRefStringEval((StringEval) eval); } - // erroreval - else if (eval instanceof ErrorEval) { - retval = eval; + if (eval instanceof ErrorEval) { + return eval; } - // refeval - else if (eval instanceof RefEval) { - RefEval re = (RefEval) eval; - retval = xlateRefEval(re); + if (eval instanceof BlankEval) { + return xlateBlankEval(REF_BLANK_IS_PARSED); } - else if (eval instanceof BlankEval) { - retval = xlateBlankEval(reval.isEvaluated() ? EVALUATED_REF_BLANK_IS_PARSED : REF_BLANK_IS_PARSED); - } - - // probably AreaEval ? then not acceptable. - else { - throw new RuntimeException("Invalid ValueEval type passed for conversion: " + eval.getClass()); - } - - - - - return retval; + throw new RuntimeException("Invalid ValueEval type passed for conversion: (" + + eval.getClass().getName() + ")"); } /** @@ -176,93 +151,38 @@ public class ValueEvalToNumericXlator { * @param eval */ private ValueEval xlateStringEval(StringEval eval) { - ValueEval retval = null; + if ((flags & STRING_IS_PARSED) > 0) { String s = eval.getStringValue(); - try { - double d = Double.parseDouble(s); - retval = new NumberEval(d); - } - catch (Exception e) { - if ((flags & STRING_TO_BOOL_IS_PARSED) > 0) { - try { - boolean b = Boolean.getBoolean(s); - retval = b ? BoolEval.TRUE : BoolEval.FALSE; - } - catch (Exception e2) { retval = ErrorEval.VALUE_INVALID; } - } - else { - retval = ErrorEval.VALUE_INVALID; - } + Double d = OperandResolver.parseDouble(s); + if(d == null) { + return ErrorEval.VALUE_INVALID; } + return new NumberEval(d.doubleValue()); } - else if ((flags & STRING_TO_BOOL_IS_PARSED) > 0) { - String s = eval.getStringValue(); - try { - boolean b = Boolean.getBoolean(s); - retval = b ? BoolEval.TRUE : BoolEval.FALSE; - } - catch (Exception e) { retval = ErrorEval.VALUE_INVALID; } - } - // strings are errors? - else if ((flags & STRING_IS_INVALID_VALUE) > 0) { - retval = ErrorEval.VALUE_INVALID; + if ((flags & STRING_IS_INVALID_VALUE) > 0) { + return ErrorEval.VALUE_INVALID; } // ignore strings - else { - retval = xlateBlankEval(BLANK_IS_PARSED); - } - return retval; + return xlateBlankEval(BLANK_IS_PARSED); } /** * uses the relevant flags to decode the StringEval * @param eval */ - private ValueEval xlateRefStringEval(StringEval eval) { - ValueEval retval = null; + private ValueEval xlateRefStringEval(StringEval sve) { if ((flags & REF_STRING_IS_PARSED) > 0) { - StringEval sve = (StringEval) eval; String s = sve.getStringValue(); - try { - double d = Double.parseDouble(s); - retval = new NumberEval(d); - } - catch (Exception e) { - if ((flags & REF_STRING_TO_BOOL_IS_PARSED) > 0) { - try { - boolean b = Boolean.getBoolean(s); - retval = b ? BoolEval.TRUE : BoolEval.FALSE; - } - catch (Exception e2) { retval = ErrorEval.VALUE_INVALID; } - } - else { - retval = ErrorEval.VALUE_INVALID; - } + Double d = OperandResolver.parseDouble(s); + if(d == null) { + return ErrorEval.VALUE_INVALID; } + return new NumberEval(d.doubleValue()); } - else if ((flags & REF_STRING_TO_BOOL_IS_PARSED) > 0) { - StringEval sve = (StringEval) eval; - String s = sve.getStringValue(); - try { - boolean b = Boolean.getBoolean(s); - retval = b ? BoolEval.TRUE : BoolEval.FALSE;; - } - catch (Exception e) { retval = ErrorEval.VALUE_INVALID; } - } - - // strings are errors? - else if ((flags & REF_STRING_IS_INVALID_VALUE) > 0) { - retval = ErrorEval.VALUE_INVALID; - } - // strings are blanks - else { - retval = BlankEval.INSTANCE; - } - return retval; + return BlankEval.INSTANCE; } - } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Avedev.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Avedev.java index 592402b80..bf888b97d 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Avedev.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Avedev.java @@ -33,8 +33,8 @@ import org.apache.poi.hssf.record.formula.eval.ValueEvalToNumericXlator; public class Avedev extends MultiOperandNumericFunction { private static final ValueEvalToNumericXlator DEFAULT_NUM_XLATOR = - new ValueEvalToNumericXlator((short) (0 - // ValueEvalToNumericXlator.BOOL_IS_PARSED + new ValueEvalToNumericXlator((short) ( + ValueEvalToNumericXlator.BOOL_IS_PARSED //| ValueEvalToNumericXlator.REF_BOOL_IS_PARSED //| ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED | ValueEvalToNumericXlator.STRING_IS_PARSED @@ -44,7 +44,6 @@ public class Avedev extends MultiOperandNumericFunction { //| ValueEvalToNumericXlator.REF_STRING_TO_BOOL_IS_PARSED //| ValueEvalToNumericXlator.STRING_IS_INVALID_VALUE //| ValueEvalToNumericXlator.REF_STRING_IS_INVALID_VALUE - | ValueEvalToNumericXlator.EVALUATED_REF_BLANK_IS_PARSED )); /** diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Average.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Average.java index 349110917..404304071 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Average.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Average.java @@ -33,8 +33,8 @@ import org.apache.poi.hssf.record.formula.eval.ValueEvalToNumericXlator; public class Average extends MultiOperandNumericFunction { private static final ValueEvalToNumericXlator DEFAULT_NUM_XLATOR = - new ValueEvalToNumericXlator((short) (0 - // ValueEvalToNumericXlator.BOOL_IS_PARSED + new ValueEvalToNumericXlator((short) ( + ValueEvalToNumericXlator.BOOL_IS_PARSED //| ValueEvalToNumericXlator.REF_BOOL_IS_PARSED //| ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED | ValueEvalToNumericXlator.STRING_IS_PARSED @@ -44,7 +44,6 @@ public class Average extends MultiOperandNumericFunction { //| ValueEvalToNumericXlator.REF_STRING_TO_BOOL_IS_PARSED //| ValueEvalToNumericXlator.STRING_IS_INVALID_VALUE //| ValueEvalToNumericXlator.REF_STRING_IS_INVALID_VALUE - | ValueEvalToNumericXlator.EVALUATED_REF_BLANK_IS_PARSED )); /** diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/FinanceFunction.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/FinanceFunction.java index 8eb7e841d..c054c6dac 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/FinanceFunction.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/FinanceFunction.java @@ -14,10 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on Jun 20, 2005 - * - */ + package org.apache.poi.hssf.record.formula.functions; import org.apache.poi.hssf.record.formula.eval.BoolEval; @@ -38,13 +35,10 @@ public abstract class FinanceFunction extends NumericFunction { new ValueEvalToNumericXlator((short) (0 | ValueEvalToNumericXlator.BOOL_IS_PARSED | ValueEvalToNumericXlator.REF_BOOL_IS_PARSED - | ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED | ValueEvalToNumericXlator.STRING_IS_PARSED | ValueEvalToNumericXlator.REF_STRING_IS_PARSED - | ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED | ValueEvalToNumericXlator.BLANK_IS_PARSED | ValueEvalToNumericXlator.REF_BLANK_IS_PARSED - | ValueEvalToNumericXlator.EVALUATED_REF_BLANK_IS_PARSED //| ValueEvalToNumericXlator.REF_STRING_TO_BOOL_IS_PARSED //| ValueEvalToNumericXlator.STRING_IS_INVALID_VALUE //| ValueEvalToNumericXlator.REF_STRING_IS_INVALID_VALUE @@ -56,11 +50,11 @@ public abstract class FinanceFunction extends NumericFunction { * if they desire to return a different ValueEvalToNumericXlator instance * than the default. */ - protected ValueEvalToNumericXlator getXlator() { + protected final ValueEvalToNumericXlator getXlator() { return DEFAULT_NUM_XLATOR; } - protected ValueEval singleOperandNumericAsBoolean(Eval eval, int srcRow, short srcCol) { + protected final ValueEval singleOperandNumericAsBoolean(Eval eval, int srcRow, short srcCol) { ValueEval retval = null; retval = singleOperandEvaluate(eval, srcRow, srcCol); if (retval instanceof NumericValueEval) { @@ -74,5 +68,4 @@ public abstract class FinanceFunction extends NumericFunction { } return retval; } - } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/FreeRefFunction.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/FreeRefFunction.java new file mode 100755 index 000000000..56d285543 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/FreeRefFunction.java @@ -0,0 +1,57 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hssf.record.formula.functions; + +import org.apache.poi.hssf.record.formula.eval.Eval; +import org.apache.poi.hssf.record.formula.eval.ValueEval; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; + + +/** + * For most Excel functions, involving references ((cell, area), (2d, 3d)), the references are + * passed in as arguments, and the exact location remains fixed. However, a select few Excel + * functions have the ability to access cells that were not part of any reference passed as an + * argument.
    + * Two important functions with this feature are INDIRECT and OFFSET

    + * + * In POI, the HSSFFormulaEvaluator evaluates every cell in each reference argument before + * calling the function. This means that functions using fixed references do not need access to + * the rest of the workbook to execute. Hence the evaluate() method on the common + * interface Function does not take a workbook parameter.

    + * + * This interface recognises the requirement of some functions to freely create and evaluate + * references beyond those passed in as arguments. + * + * @author Josh Micich + */ +public interface FreeRefFunction { + /** + * + * @param args the pre-evaluated arguments for this function. args is never null, + * nor are any of its elements. + * @param srcCellRow zero based row index of the cell containing the currently evaluating formula + * @param srcCellCol zero based column index of the cell containing the currently evaluating formula + * @param workbook is the workbook containing the formula/cell being evaluated + * @param sheet is the sheet containing the formula/cell being evaluated + * @return never null. Possibly an instance of ErrorEval in the case of + * a specified Excel error (Exceptions are never thrown to represent Excel errors). + * + */ + ValueEval evaluate(Eval[] args, int srcCellRow, short srcCellCol, HSSFWorkbook workbook, HSSFSheet sheet); +} diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Hlookup.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Hlookup.java index 8bac3d0c0..40ed1da49 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Hlookup.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Hlookup.java @@ -1,25 +1,123 @@ -/* -* Licensed to the Apache Software Foundation (ASF) under one or more -* contributor license agreements. See the NOTICE file distributed with -* this work for additional information regarding copyright ownership. -* The ASF licenses this file to You under the Apache License, Version 2.0 -* (the "License"); you may not use this file except in compliance with -* the License. You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ -/* - * Created on May 15, 2005 - * - */ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + package org.apache.poi.hssf.record.formula.functions; -public class Hlookup extends NotImplementedFunction { +import org.apache.poi.hssf.record.formula.eval.AreaEval; +import org.apache.poi.hssf.record.formula.eval.ErrorEval; +import org.apache.poi.hssf.record.formula.eval.Eval; +import org.apache.poi.hssf.record.formula.eval.EvaluationException; +import org.apache.poi.hssf.record.formula.eval.OperandResolver; +import org.apache.poi.hssf.record.formula.eval.ValueEval; +import org.apache.poi.hssf.record.formula.functions.LookupUtils.ValueVector; +/** + * Implementation of the VLOOKUP() function.

    + * + * HLOOKUP finds a column in a lookup table by the first row value and returns the value from another row. + * + * Syntax:
    + * HLOOKUP(lookup_value, table_array, row_index_num, range_lookup)

    + * + * lookup_value The value to be found in the first column of the table array.
    + * table_array An area reference for the lookup data.
    + * row_index_num a 1 based index specifying which row value of the lookup data will be returned.
    + * range_lookup If TRUE (default), HLOOKUP finds the largest value less than or equal to + * the lookup_value. If FALSE, only exact matches will be considered
    + * + * @author Josh Micich + */ +public final class Hlookup implements Function { + + private static final class RowVector implements ValueVector { + private final AreaEval _tableArray; + private final int _size; + private final int _rowAbsoluteIndex; + private final int _firstColumnAbsoluteIndex; + + public RowVector(AreaEval tableArray, int rowIndex) { + _rowAbsoluteIndex = tableArray.getFirstRow() + rowIndex; + if(!tableArray.containsRow(_rowAbsoluteIndex)) { + int lastRowIx = tableArray.getLastRow() - tableArray.getFirstRow(); + throw new IllegalArgumentException("Specified row index (" + rowIndex + + ") is outside the allowed range (0.." + lastRowIx + ")"); + } + _tableArray = tableArray; + _size = tableArray.getLastColumn() - tableArray.getFirstColumn() + 1; + if(_size < 1) { + throw new RuntimeException("bad table array size zero"); + } + _firstColumnAbsoluteIndex = tableArray.getFirstColumn(); + } + + public ValueEval getItem(int index) { + if(index>_size) { + throw new ArrayIndexOutOfBoundsException("Specified index (" + index + + ") is outside the allowed range (0.." + (_size-1) + ")"); + } + return _tableArray.getValueAt(_rowAbsoluteIndex, (short) (_firstColumnAbsoluteIndex + index)); + } + public int getSize() { + return _size; + } + } + + public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) { + Eval arg3 = null; + switch(args.length) { + case 4: + arg3 = args[3]; // important: assumed array element is never null + case 3: + break; + default: + // wrong number of arguments + return ErrorEval.VALUE_INVALID; + } + try { + // Evaluation order: + // arg0 lookup_value, arg1 table_array, arg3 range_lookup, find lookup value, arg2 row_index, fetch result + ValueEval lookupValue = OperandResolver.getSingleValue(args[0], srcCellRow, srcCellCol); + AreaEval tableArray = LookupUtils.resolveTableArrayArg(args[1]); + boolean isRangeLookup = LookupUtils.resolveRangeLookupArg(arg3, srcCellRow, srcCellCol); + int colIndex = LookupUtils.lookupIndexOfValue(lookupValue, new RowVector(tableArray, 0), isRangeLookup); + ValueEval veColIndex = OperandResolver.getSingleValue(args[2], srcCellRow, srcCellCol); + int rowIndex = LookupUtils.resolveRowOrColIndexArg(veColIndex); + ValueVector resultCol = createResultColumnVector(tableArray, rowIndex); + return resultCol.getItem(colIndex); + } catch (EvaluationException e) { + return e.getErrorEval(); + } + } + + + /** + * Returns one column from an AreaEval + * + * @throws EvaluationException (#VALUE!) if colIndex is negative, (#REF!) if colIndex is too high + */ + private ValueVector createResultColumnVector(AreaEval tableArray, int colIndex) throws EvaluationException { + if(colIndex < 0) { + throw EvaluationException.invalidValue(); + } + int nCols = tableArray.getLastColumn() - tableArray.getFirstRow() + 1; + + if(colIndex >= nCols) { + throw EvaluationException.invalidRef(); + } + return new RowVector(tableArray, colIndex); + } } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/If.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/If.java index 90dbd591b..7aba5db72 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/If.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/If.java @@ -28,28 +28,22 @@ import org.apache.poi.hssf.record.formula.eval.Eval; * @author Amol S. Deshmukh < amolweb at ya hoo dot com > * */ -public class If implements Function { +public final class If implements Function { + + public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) { - public Eval evaluate(Eval[] evals, int srcCellRow, short srcCellCol) { - Eval retval = null; Eval evalWhenFalse = BoolEval.FALSE; - switch (evals.length) { + switch (args.length) { case 3: - evalWhenFalse = evals[2]; + evalWhenFalse = args[2]; case 2: - BoolEval beval = (BoolEval) evals[0]; + BoolEval beval = (BoolEval) args[0]; // TODO - class cast exception if (beval.getBooleanValue()) { - retval = evals[1]; + return args[1]; } - else { - retval = evalWhenFalse; - } - break; + return evalWhenFalse; default: - retval = ErrorEval.UNKNOWN_ERROR; + return ErrorEval.VALUE_INVALID; } - return retval; } - - } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Indirect.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Indirect.java index c7464ffed..935e7cdbb 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Indirect.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Indirect.java @@ -14,12 +14,36 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on May 15, 2005 - * - */ + package org.apache.poi.hssf.record.formula.functions; -public class Indirect extends NotImplementedFunction { +import org.apache.poi.hssf.record.formula.eval.ErrorEval; +import org.apache.poi.hssf.record.formula.eval.Eval; +import org.apache.poi.hssf.record.formula.eval.ValueEval; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; + +/** + * Implementation for Excel function INDIRECT

    + * + * INDIRECT() returns the cell or area reference denoted by the text argument.

    + * + * Syntax:
    + * INDIRECT(ref_text,isA1Style)

    + * + * ref_text a string representation of the desired reference as it would normally be written + * in a cell formula.
    + * isA1Style (default TRUE) specifies whether the ref_text should be interpreted as A1-style + * or R1C1-style. + * + * + * @author Josh Micich + */ +public final class Indirect implements FreeRefFunction { + + public ValueEval evaluate(Eval[] args, int srcCellRow, short srcCellCol, HSSFWorkbook workbook, HSSFSheet sheet) { + // TODO - implement INDIRECT() + return ErrorEval.FUNCTION_NOT_IMPLEMENTED; + } } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Isblank.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Isblank.java index 6e8f84b34..c0e482e5a 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Isblank.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Isblank.java @@ -14,79 +14,35 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on May 15, 2005 - * - */ + package org.apache.poi.hssf.record.formula.functions; -import org.apache.poi.hssf.record.formula.eval.AreaEval; import org.apache.poi.hssf.record.formula.eval.BlankEval; import org.apache.poi.hssf.record.formula.eval.BoolEval; import org.apache.poi.hssf.record.formula.eval.ErrorEval; import org.apache.poi.hssf.record.formula.eval.Eval; -import org.apache.poi.hssf.record.formula.eval.RefEval; +import org.apache.poi.hssf.record.formula.eval.EvaluationException; +import org.apache.poi.hssf.record.formula.eval.OperandResolver; import org.apache.poi.hssf.record.formula.eval.ValueEval; /** * @author Amol S. Deshmukh < amolweb at ya hoo dot com > * */ -public class Isblank implements Function { +public final class Isblank implements Function { - public Eval evaluate(Eval[] operands, int srcCellRow, short srcCellCol) { - ValueEval retval = null; - boolean b = false; - - switch (operands.length) { - default: - retval = ErrorEval.VALUE_INVALID; - break; - case 1: - if (operands[0] instanceof BlankEval) { - b = true; - } - else if (operands[0] instanceof AreaEval) { - AreaEval ae = (AreaEval) operands[0]; - if (ae.contains(srcCellRow, srcCellCol)) { // circular ref! - retval = ErrorEval.CIRCULAR_REF_ERROR; - } - else if (ae.isRow()) { - if (ae.containsColumn(srcCellCol)) { - ValueEval ve = ae.getValueAt(ae.getFirstRow(), srcCellCol); - b = (ve instanceof BlankEval); - } - else { - b = false; - } - } - else if (ae.isColumn()) { - if (ae.containsRow(srcCellRow)) { - ValueEval ve = ae.getValueAt(srcCellRow, ae.getFirstColumn()); - b = (ve instanceof BlankEval); - } - else { - b = false; - } - } - else { - b = false; - } - } - else if (operands[0] instanceof RefEval) { - RefEval re = (RefEval) operands[0]; - b = (!re.isEvaluated()) && re.getInnerValueEval() instanceof BlankEval; - } - else { - b = false; - } - } - - if (retval == null) { - retval = b - ? BoolEval.TRUE - : BoolEval.FALSE; - } - return retval; - } + public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) { + if(args.length != 1) { + return ErrorEval.VALUE_INVALID; + } + Eval arg = args[0]; + + ValueEval singleCellValue; + try { + singleCellValue = OperandResolver.getSingleValue(arg, srcCellRow, srcCellCol); + } catch (EvaluationException e) { + return BoolEval.FALSE; + } + return BoolEval.valueOf(singleCellValue instanceof BlankEval); + } } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Len.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Len.java index c0cb39b26..0bc49b407 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Len.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Len.java @@ -14,125 +14,36 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on May 15, 2005 - * - */ + package org.apache.poi.hssf.record.formula.functions; -import org.apache.poi.hssf.record.formula.eval.AreaEval; -import org.apache.poi.hssf.record.formula.eval.BlankEval; import org.apache.poi.hssf.record.formula.eval.ErrorEval; import org.apache.poi.hssf.record.formula.eval.Eval; +import org.apache.poi.hssf.record.formula.eval.EvaluationException; import org.apache.poi.hssf.record.formula.eval.NumberEval; -import org.apache.poi.hssf.record.formula.eval.RefEval; -import org.apache.poi.hssf.record.formula.eval.StringValueEval; +import org.apache.poi.hssf.record.formula.eval.OperandResolver; import org.apache.poi.hssf.record.formula.eval.ValueEval; /** * @author Amol S. Deshmukh < amolweb at ya hoo dot com > * */ -public class Len extends TextFunction { +public final class Len extends TextFunction { + public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) { + + if(args.length != 1) { + return ErrorEval.VALUE_INVALID; + } + + try { + ValueEval veval = OperandResolver.getSingleValue(args[0], srcCellRow, srcCellCol); - public Eval evaluate(Eval[] operands, int srcCellRow, short srcCellCol) { - ValueEval retval = null; - String s = null; - - switch (operands.length) { - default: - retval = ErrorEval.VALUE_INVALID; - break; - case 1: - ValueEval ve = singleOperandEvaluate(operands[0], srcCellRow, srcCellCol); - if (ve instanceof StringValueEval) { - StringValueEval sve = (StringValueEval) ve; - s = sve.getStringValue(); - } - else if (ve instanceof RefEval) { - RefEval re = (RefEval) ve; - ValueEval ive = re.getInnerValueEval(); - if (ive instanceof BlankEval) { - s = re.isEvaluated() ? "0" : null; - } - else if (ive instanceof StringValueEval) { - s = ((StringValueEval) ive).getStringValue(); - } - else if (ive instanceof BlankEval) {} - else { - retval = ErrorEval.VALUE_INVALID; - } - } - else if (ve instanceof BlankEval) {} - else { - retval = ErrorEval.VALUE_INVALID; - break; - } - } - - if (retval == null) { - s = (s == null) ? EMPTY_STRING : s; - retval = new NumberEval(s.length()); - } - - return retval; - } - - - protected ValueEval singleOperandEvaluate(Eval eval, int srcRow, short srcCol) { - ValueEval retval; - if (eval instanceof AreaEval) { - AreaEval ae = (AreaEval) eval; - if (ae.contains(srcRow, srcCol)) { // circular ref! - retval = ErrorEval.CIRCULAR_REF_ERROR; - } - else if (ae.isRow()) { - if (ae.containsColumn(srcCol)) { - ValueEval ve = ae.getValueAt(ae.getFirstRow(), srcCol); - retval = attemptXlateToText(ve); - } - else { - retval = ErrorEval.VALUE_INVALID; - } - } - else if (ae.isColumn()) { - if (ae.containsRow(srcRow)) { - ValueEval ve = ae.getValueAt(srcRow, ae.getFirstColumn()); - retval = attemptXlateToText(ve); - } - else { - retval = ErrorEval.VALUE_INVALID; - } - } - else { - retval = ErrorEval.VALUE_INVALID; - } - } - else { - retval = attemptXlateToText((ValueEval) eval); - } - return retval; - } - - - /** - * converts from Different ValueEval types to StringEval. - * Note: AreaEvals are not handled, if arg is an AreaEval, - * the returned value is ErrorEval.VALUE_INVALID - * @param ve - */ - protected ValueEval attemptXlateToText(ValueEval ve) { - ValueEval retval; - if (ve instanceof StringValueEval || ve instanceof RefEval) { - retval = ve; - } - else if (ve instanceof BlankEval) { - retval = ve; - } - else { - retval = ErrorEval.VALUE_INVALID; - } - return retval; - } + String str = OperandResolver.coerceValueToString(veval); + + return new NumberEval(str.length()); + } catch (EvaluationException e) { + return e.getErrorEval(); + } + } } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Lookup.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Lookup.java index f98ccca7e..be1d0d0f9 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Lookup.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Lookup.java @@ -1,25 +1,96 @@ -/* -* Licensed to the Apache Software Foundation (ASF) under one or more -* contributor license agreements. See the NOTICE file distributed with -* this work for additional information regarding copyright ownership. -* The ASF licenses this file to You under the Apache License, Version 2.0 -* (the "License"); you may not use this file except in compliance with -* the License. You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ -/* - * Created on May 15, 2005 - * - */ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + package org.apache.poi.hssf.record.formula.functions; -public class Lookup extends NotImplementedFunction { +import org.apache.poi.hssf.record.formula.eval.AreaEval; +import org.apache.poi.hssf.record.formula.eval.ErrorEval; +import org.apache.poi.hssf.record.formula.eval.Eval; +import org.apache.poi.hssf.record.formula.eval.EvaluationException; +import org.apache.poi.hssf.record.formula.eval.OperandResolver; +import org.apache.poi.hssf.record.formula.eval.ValueEval; +import org.apache.poi.hssf.record.formula.functions.LookupUtils.ValueVector; +/** + * Implementation of Excel function LOOKUP.

    + * + * LOOKUP finds an index row in a lookup table by the first column value and returns the value from another column. + * + * Syntax:
    + * VLOOKUP(lookup_value, lookup_vector, result_vector)

    + * + * lookup_value The value to be found in the lookup vector.
    + * lookup_vector An area reference for the lookup data.
    + * result_vector Single row or single column area reference from which the result value is chosen.
    + * + * @author Josh Micich + */ +public final class Lookup implements Function { + private static final class SimpleValueVector implements ValueVector { + private final ValueEval[] _values; + + public SimpleValueVector(ValueEval[] values) { + _values = values; + } + public ValueEval getItem(int index) { + return _values[index]; + } + public int getSize() { + return _values.length; + } + } + + public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) { + switch(args.length) { + case 3: + break; + case 2: + // complex rules to choose lookupVector and resultVector from the single area ref + throw new RuntimeException("Two arg version of LOOKUP not supported yet"); + default: + return ErrorEval.VALUE_INVALID; + } + + + try { + ValueEval lookupValue = OperandResolver.getSingleValue(args[0], srcCellRow, srcCellCol); + AreaEval aeLookupVector = LookupUtils.resolveTableArrayArg(args[1]); + AreaEval aeResultVector = LookupUtils.resolveTableArrayArg(args[2]); + + ValueVector lookupVector = createVector(aeLookupVector); + ValueVector resultVector = createVector(aeResultVector); + if(lookupVector.getSize() > resultVector.getSize()) { + // Excel seems to handle this by accessing past the end of the result vector. + throw new RuntimeException("Lookup vector and result vector of differing sizes not supported yet"); + } + int index = LookupUtils.lookupIndexOfValue(lookupValue, lookupVector, true); + + return resultVector.getItem(index); + } catch (EvaluationException e) { + return e.getErrorEval(); + } + } + + private static ValueVector createVector(AreaEval ae) { + + if(!ae.isRow() && !ae.isColumn()) { + // extra complexity required to emulate the way LOOKUP can handles these abnormal cases. + throw new RuntimeException("non-vector lookup or result areas not supported yet"); + } + return new SimpleValueVector(ae.getValues()); + } } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/LookupUtils.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/LookupUtils.java new file mode 100644 index 000000000..d6a848962 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/LookupUtils.java @@ -0,0 +1,530 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hssf.record.formula.functions; + +import org.apache.poi.hssf.record.formula.AreaPtg; +import org.apache.poi.hssf.record.formula.eval.Area2DEval; +import org.apache.poi.hssf.record.formula.eval.AreaEval; +import org.apache.poi.hssf.record.formula.eval.BlankEval; +import org.apache.poi.hssf.record.formula.eval.BoolEval; +import org.apache.poi.hssf.record.formula.eval.ErrorEval; +import org.apache.poi.hssf.record.formula.eval.Eval; +import org.apache.poi.hssf.record.formula.eval.EvaluationException; +import org.apache.poi.hssf.record.formula.eval.NumberEval; +import org.apache.poi.hssf.record.formula.eval.NumericValueEval; +import org.apache.poi.hssf.record.formula.eval.OperandResolver; +import org.apache.poi.hssf.record.formula.eval.RefEval; +import org.apache.poi.hssf.record.formula.eval.StringEval; +import org.apache.poi.hssf.record.formula.eval.ValueEval; + +/** + * Common functionality used by VLOOKUP, HLOOKUP, LOOKUP and MATCH + * + * @author Josh Micich + */ +final class LookupUtils { + + /** + * Represents a single row or column within an AreaEval. + */ + public interface ValueVector { + ValueEval getItem(int index); + int getSize(); + } + /** + * Enumeration to support 4 valued comparison results.

    + * Excel lookup functions have complex behaviour in the case where the lookup array has mixed + * types, and/or is unordered. Contrary to suggestions in some Excel documentation, there + * does not appear to be a universal ordering across types. The binary search algorithm used + * changes behaviour when the evaluated 'mid' value has a different type to the lookup value.

    + * + * A simple int might have done the same job, but there is risk in confusion with the well + * known Comparable.compareTo() and Comparator.compare() which both use + * a ubiquitous 3 value result encoding. + */ + public static final class CompareResult { + private final boolean _isTypeMismatch; + private final boolean _isLessThan; + private final boolean _isEqual; + private final boolean _isGreaterThan; + + private CompareResult(boolean isTypeMismatch, int simpleCompareResult) { + if(isTypeMismatch) { + _isTypeMismatch = true; + _isLessThan = false; + _isEqual = false; + _isGreaterThan = false; + } else { + _isTypeMismatch = false; + _isLessThan = simpleCompareResult < 0; + _isEqual = simpleCompareResult == 0; + _isGreaterThan = simpleCompareResult > 0; + } + } + public static final CompareResult TYPE_MISMATCH = new CompareResult(true, 0); + public static final CompareResult LESS_THAN = new CompareResult(false, -1); + public static final CompareResult EQUAL = new CompareResult(false, 0); + public static final CompareResult GREATER_THAN = new CompareResult(false, +1); + + public static final CompareResult valueOf(int simpleCompareResult) { + if(simpleCompareResult < 0) { + return LESS_THAN; + } + if(simpleCompareResult > 0) { + return GREATER_THAN; + } + return EQUAL; + } + + public boolean isTypeMismatch() { + return _isTypeMismatch; + } + public boolean isLessThan() { + return _isLessThan; + } + public boolean isEqual() { + return _isEqual; + } + public boolean isGreaterThan() { + return _isGreaterThan; + } + public String toString() { + StringBuffer sb = new StringBuffer(64); + sb.append(getClass().getName()).append(" ["); + sb.append(formatAsString()); + sb.append("]"); + return sb.toString(); + } + + private String formatAsString() { + if(_isTypeMismatch) { + return "TYPE_MISMATCH"; + } + if(_isLessThan) { + return "LESS_THAN"; + } + if(_isEqual) { + return "EQUAL"; + } + if(_isGreaterThan) { + return "GREATER_THAN"; + } + // toString must be reliable + return "??error??"; + } + } + + public interface LookupValueComparer { + /** + * @return one of 4 instances or CompareResult: LESS_THAN, EQUAL, + * GREATER_THAN or TYPE_MISMATCH + */ + CompareResult compareTo(ValueEval other); + } + + private static abstract class LookupValueComparerBase implements LookupValueComparer { + + private final Class _targetClass; + protected LookupValueComparerBase(ValueEval targetValue) { + if(targetValue == null) { + throw new RuntimeException("targetValue cannot be null"); + } + _targetClass = targetValue.getClass(); + } + public final CompareResult compareTo(ValueEval other) { + if (other == null) { + throw new RuntimeException("compare to value cannot be null"); + } + if (_targetClass != other.getClass()) { + return CompareResult.TYPE_MISMATCH; + } + if (_targetClass == StringEval.class) { + + } + return compareSameType(other); + } + public String toString() { + StringBuffer sb = new StringBuffer(64); + sb.append(getClass().getName()).append(" ["); + sb.append(getValueAsString()); + sb.append("]"); + return sb.toString(); + } + protected abstract CompareResult compareSameType(ValueEval other); + /** used only for debug purposes */ + protected abstract String getValueAsString(); + } + + private static final class StringLookupComparer extends LookupValueComparerBase { + private String _value; + + protected StringLookupComparer(StringEval se) { + super(se); + _value = se.getStringValue(); + } + protected CompareResult compareSameType(ValueEval other) { + StringEval se = (StringEval) other; + return CompareResult.valueOf(_value.compareToIgnoreCase(se.getStringValue())); + } + protected String getValueAsString() { + return _value; + } + } + private static final class NumberLookupComparer extends LookupValueComparerBase { + private double _value; + + protected NumberLookupComparer(NumberEval ne) { + super(ne); + _value = ne.getNumberValue(); + } + protected CompareResult compareSameType(ValueEval other) { + NumberEval ne = (NumberEval) other; + return CompareResult.valueOf(Double.compare(_value, ne.getNumberValue())); + } + protected String getValueAsString() { + return String.valueOf(_value); + } + } + private static final class BooleanLookupComparer extends LookupValueComparerBase { + private boolean _value; + + protected BooleanLookupComparer(BoolEval be) { + super(be); + _value = be.getBooleanValue(); + } + protected CompareResult compareSameType(ValueEval other) { + BoolEval be = (BoolEval) other; + boolean otherVal = be.getBooleanValue(); + if(_value == otherVal) { + return CompareResult.EQUAL; + } + // TRUE > FALSE + if(_value) { + return CompareResult.GREATER_THAN; + } + return CompareResult.LESS_THAN; + } + protected String getValueAsString() { + return String.valueOf(_value); + } + } + + /** + * Processes the third argument to VLOOKUP, or HLOOKUP (col_index_num + * or row_index_num respectively).
    + * Sample behaviour: + * + * + * + * + * + * + * + * + * + * + * + * + * + *
    Input   ReturnValue  Thrown Error
    54 
    2.92 
    "5"4 
    "2.18e1"21 
    "-$2"-3*
    FALSE-1*
    TRUE0 
    "TRUE" #REF!
    "abc" #REF!
    "" #REF!
    <blank> #VALUE!

    + * + * * Note - out of range errors (both too high and too low) are handled by the caller. + * @return column or row index as a zero-based value + * + */ + public static int resolveRowOrColIndexArg(ValueEval veRowColIndexArg) throws EvaluationException { + if(veRowColIndexArg == null) { + throw new IllegalArgumentException("argument must not be null"); + } + if(veRowColIndexArg instanceof BlankEval) { + throw EvaluationException.invalidValue(); + } + if(veRowColIndexArg instanceof StringEval) { + StringEval se = (StringEval) veRowColIndexArg; + String strVal = se.getStringValue(); + Double dVal = OperandResolver.parseDouble(strVal); + if(dVal == null) { + // String does not resolve to a number. Raise #VALUE! error. + throw EvaluationException.invalidRef(); + // This includes text booleans "TRUE" and "FALSE". They are not valid. + } + // else - numeric value parses OK + } + // actual BoolEval values get interpreted as FALSE->0 and TRUE->1 + return OperandResolver.coerceValueToInt(veRowColIndexArg) - 1; + } + + + + /** + * The second argument (table_array) should be an area ref, but can actually be a cell ref, in + * which case it is interpreted as a 1x1 area ref. Other scalar values cause #VALUE! error. + */ + public static AreaEval resolveTableArrayArg(Eval eval) throws EvaluationException { + if (eval instanceof AreaEval) { + return (AreaEval) eval; + } + + if(eval instanceof RefEval) { + RefEval refEval = (RefEval) eval; + // Make this cell ref look like a 1x1 area ref. + + // It doesn't matter if eval is a 2D or 3D ref, because that detail is never asked of AreaEval. + // This code only requires the value array item. + // anything would be ok for rowIx and colIx, but may as well get it right. + int rowIx = refEval.getRow(); + int colIx = refEval.getColumn(); + AreaPtg ap = new AreaPtg(rowIx, rowIx, colIx, colIx, false, false, false, false); + ValueEval value = refEval.getInnerValueEval(); + return new Area2DEval(ap, new ValueEval[] { value, }); + } + throw EvaluationException.invalidValue(); + } + + + /** + * Resolves the last (optional) parameter (range_lookup) to the VLOOKUP and HLOOKUP functions. + * @param rangeLookupArg + * @param srcCellRow + * @param srcCellCol + * @return + * @throws EvaluationException + */ + public static boolean resolveRangeLookupArg(Eval rangeLookupArg, int srcCellRow, short srcCellCol) throws EvaluationException { + if(rangeLookupArg == null) { + // range_lookup arg not provided + return true; // default is TRUE + } + ValueEval valEval = OperandResolver.getSingleValue(rangeLookupArg, srcCellRow, srcCellCol); + if(valEval instanceof BlankEval) { + // Tricky: + // fourth arg supplied but evaluates to blank + // this does not get the default value + return false; + } + if(valEval instanceof BoolEval) { + // Happy day flow + BoolEval boolEval = (BoolEval) valEval; + return boolEval.getBooleanValue(); + } + + if (valEval instanceof StringEval) { + String stringValue = ((StringEval) valEval).getStringValue(); + if(stringValue.length() < 1) { + // More trickiness: + // Empty string is not the same as BlankEval. It causes #VALUE! error + throw EvaluationException.invalidValue(); + } + // TODO move parseBoolean to OperandResolver + Boolean b = Countif.parseBoolean(stringValue); + if(b != null) { + // string converted to boolean OK + return b.booleanValue(); + } + // Even more trickiness: + // Note - even if the StringEval represents a number value (for example "1"), + // Excel does not resolve it to a boolean. + throw EvaluationException.invalidValue(); + // This is in contrast to the code below,, where NumberEvals values (for + // example 0.01) *do* resolve to equivalent boolean values. + } + if (valEval instanceof NumericValueEval) { + NumericValueEval nve = (NumericValueEval) valEval; + // zero is FALSE, everything else is TRUE + return 0.0 != nve.getNumberValue(); + } + throw new RuntimeException("Unexpected eval type (" + valEval.getClass().getName() + ")"); + } + + public static int lookupIndexOfValue(ValueEval lookupValue, ValueVector vector, boolean isRangeLookup) throws EvaluationException { + LookupValueComparer lookupComparer = createLookupComparer(lookupValue); + int result; + if(isRangeLookup) { + result = performBinarySearch(vector, lookupComparer); + } else { + result = lookupIndexOfExactValue(lookupComparer, vector); + } + if(result < 0) { + throw new EvaluationException(ErrorEval.NA); + } + return result; + } + + + /** + * Finds first (lowest index) exact occurrence of specified value. + * @param lookupValue the value to be found in column or row vector + * @param vector the values to be searched. For VLOOKUP this is the first column of the + * tableArray. For HLOOKUP this is the first row of the tableArray. + * @return zero based index into the vector, -1 if value cannot be found + */ + private static int lookupIndexOfExactValue(LookupValueComparer lookupComparer, ValueVector vector) { + + // find first occurrence of lookup value + int size = vector.getSize(); + for (int i = 0; i < size; i++) { + if(lookupComparer.compareTo(vector.getItem(i)).isEqual()) { + return i; + } + } + return -1; + } + + + /** + * Encapsulates some standard binary search functionality so the unusual Excel behaviour can + * be clearly distinguished. + */ + private static final class BinarySearchIndexes { + + private int _lowIx; + private int _highIx; + + public BinarySearchIndexes(int highIx) { + _lowIx = -1; + _highIx = highIx; + } + + /** + * @return -1 if the search range is empty + */ + public int getMidIx() { + int ixDiff = _highIx - _lowIx; + if(ixDiff < 2) { + return -1; + } + return _lowIx + (ixDiff / 2); + } + + public int getLowIx() { + return _lowIx; + } + public int getHighIx() { + return _highIx; + } + public void narrowSearch(int midIx, boolean isLessThan) { + if(isLessThan) { + _highIx = midIx; + } else { + _lowIx = midIx; + } + } + } + /** + * Excel has funny behaviour when the some elements in the search vector are the wrong type. + * + */ + private static int performBinarySearch(ValueVector vector, LookupValueComparer lookupComparer) { + // both low and high indexes point to values assumed too low and too high. + BinarySearchIndexes bsi = new BinarySearchIndexes(vector.getSize()); + + while(true) { + int midIx = bsi.getMidIx(); + + if(midIx < 0) { + return bsi.getLowIx(); + } + CompareResult cr = lookupComparer.compareTo(vector.getItem(midIx)); + if(cr.isTypeMismatch()) { + int newMidIx = handleMidValueTypeMismatch(lookupComparer, vector, bsi, midIx); + if(newMidIx < 0) { + continue; + } + midIx = newMidIx; + cr = lookupComparer.compareTo(vector.getItem(midIx)); + } + if(cr.isEqual()) { + return findLastIndexInRunOfEqualValues(lookupComparer, vector, midIx, bsi.getHighIx()); + } + bsi.narrowSearch(midIx, cr.isLessThan()); + } + } + /** + * Excel seems to handle mismatched types initially by just stepping 'mid' ix forward to the + * first compatible value. + * @param midIx 'mid' index (value which has the wrong type) + * @return usually -1, signifying that the BinarySearchIndex has been narrowed to the new mid + * index. Zero or greater signifies that an exact match for the lookup value was found + */ + private static int handleMidValueTypeMismatch(LookupValueComparer lookupComparer, ValueVector vector, + BinarySearchIndexes bsi, int midIx) { + int newMid = midIx; + int highIx = bsi.getHighIx(); + + while(true) { + newMid++; + if(newMid == highIx) { + // every element from midIx to highIx was the wrong type + // move highIx down to the low end of the mid values + bsi.narrowSearch(midIx, true); + return -1; + } + CompareResult cr = lookupComparer.compareTo(vector.getItem(newMid)); + if(cr.isLessThan() && newMid == highIx-1) { + // move highIx down to the low end of the mid values + bsi.narrowSearch(midIx, true); + return -1; + // but only when "newMid == highIx-1"? slightly weird. + // It would seem more efficient to always do this. + } + if(cr.isTypeMismatch()) { + // keep stepping over values until the right type is found + continue; + } + if(cr.isEqual()) { + return newMid; + } + // Note - if moving highIx down (due to lookup + * + * Syntax:
    + * MATCH(lookup_value, lookup_array, match_type)

    + * + * Returns a 1-based index specifying at what position in the lookup_array the specified + * lookup_value is found.

    + * + * Specific matching behaviour can be modified with the optional match_type parameter. + * + * + * + * + * + * + *
    ValueMatching Behaviour
    1(default) find the largest value that is less than or equal to lookup_value. + * The lookup_array must be in ascending order*.
    0find the first value that is exactly equal to lookup_value. + * The lookup_array can be in any order.
    -1find the smallest value that is greater than or equal to lookup_value. + * The lookup_array must be in descending order*.
    + * + * * Note regarding order - For the match_type cases that require the lookup_array to + * be ordered, MATCH() can produce incorrect results if this requirement is not met. Observed + * behaviour in Excel is to return the lowest index value for which every item after that index + * breaks the match rule.
    + * The (ascending) sort order expected by MATCH() is:
    + * numbers (low to high), strings (A to Z), boolean (FALSE to TRUE)
    + * MATCH() ignores all elements in the lookup_array with a different type to the lookup_value. + * Type conversion of the lookup_array elements is never performed. + * + * + * @author Josh Micich + */ +public final class Match implements Function { + + + public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) { + + double match_type = 1; // default + + switch(args.length) { + case 3: + try { + match_type = evaluateMatchTypeArg(args[2], srcCellRow, srcCellCol); + } catch (EvaluationException e) { + // Excel/MATCH() seems to have slightly abnormal handling of errors with + // the last parameter. Errors do not propagate up. Every error gets + // translated into #REF! + return ErrorEval.REF_INVALID; + } + case 2: + break; + default: + return ErrorEval.VALUE_INVALID; + } + + boolean matchExact = match_type == 0; + // Note - Excel does not strictly require -1 and +1 + boolean findLargestLessThanOrEqual = match_type > 0; + + + try { + ValueEval lookupValue = OperandResolver.getSingleValue(args[0], srcCellRow, srcCellCol); + ValueEval[] lookupRange = evaluateLookupRange(args[1]); + int index = findIndexOfValue(lookupValue, lookupRange, matchExact, findLargestLessThanOrEqual); + return new NumberEval(index + 1); // +1 to convert to 1-based + } catch (EvaluationException e) { + return e.getErrorEval(); + } + } + + private static ValueEval[] evaluateLookupRange(Eval eval) throws EvaluationException { + if (eval instanceof RefEval) { + RefEval re = (RefEval) eval; + return new ValueEval[] { re.getInnerValueEval(), }; + } + if (eval instanceof AreaEval) { + AreaEval ae = (AreaEval) eval; + if(!ae.isColumn() && !ae.isRow()) { + throw new EvaluationException(ErrorEval.NA); + } + return ae.getValues(); + } + + // Error handling for lookup_range arg is also unusual + if(eval instanceof NumericValueEval) { + throw new EvaluationException(ErrorEval.NA); + } + if (eval instanceof StringEval) { + StringEval se = (StringEval) eval; + Double d = OperandResolver.parseDouble(se.getStringValue()); + if(d == null) { + // plain string + throw new EvaluationException(ErrorEval.VALUE_INVALID); + } + // else looks like a number + throw new EvaluationException(ErrorEval.NA); + } + throw new RuntimeException("Unexpected eval type (" + eval.getClass().getName() + ")"); + } + + + + private static double evaluateMatchTypeArg(Eval arg, int srcCellRow, short srcCellCol) + throws EvaluationException { + Eval match_type = OperandResolver.getSingleValue(arg, srcCellRow, srcCellCol); + + if(match_type instanceof ErrorEval) { + throw new EvaluationException((ErrorEval)match_type); + } + if(match_type instanceof NumericValueEval) { + NumericValueEval ne = (NumericValueEval) match_type; + return ne.getNumberValue(); + } + if (match_type instanceof StringEval) { + StringEval se = (StringEval) match_type; + Double d = OperandResolver.parseDouble(se.getStringValue()); + if(d == null) { + // plain string + throw new EvaluationException(ErrorEval.VALUE_INVALID); + } + // if the string parses as a number, it is OK + return d.doubleValue(); + } + throw new RuntimeException("Unexpected match_type type (" + match_type.getClass().getName() + ")"); + } + + /** + * @return zero based index + */ + private static int findIndexOfValue(ValueEval lookupValue, ValueEval[] lookupRange, + boolean matchExact, boolean findLargestLessThanOrEqual) throws EvaluationException { + + LookupValueComparer lookupComparer = createLookupComparer(lookupValue, matchExact); + + if(matchExact) { + for (int i = 0; i < lookupRange.length; i++) { + if(lookupComparer.compareTo(lookupRange[i]).isEqual()) { + return i; + } + } + throw new EvaluationException(ErrorEval.NA); + } + + if(findLargestLessThanOrEqual) { + // Note - backward iteration + for (int i = lookupRange.length - 1; i>=0; i--) { + CompareResult cmp = lookupComparer.compareTo(lookupRange[i]); + if(cmp.isTypeMismatch()) { + continue; + } + if(!cmp.isLessThan()) { + return i; + } + } + throw new EvaluationException(ErrorEval.NA); + } + + // else - find smallest greater than or equal to + // TODO - is binary search used for (match_type==+1) ? + for (int i = 0; i=0 || stringValue.indexOf('*') >=0) { + return true; + } + return false; + } } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Maxa.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Maxa.java index 21e30de0c..e25db7b74 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Maxa.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Maxa.java @@ -14,10 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on May 15, 2005 - * - */ + package org.apache.poi.hssf.record.formula.functions; import org.apache.poi.hssf.record.formula.eval.ErrorEval; @@ -30,12 +27,11 @@ import org.apache.poi.hssf.record.formula.eval.ValueEvalToNumericXlator; * @author Amol S. Deshmukh < amolweb at ya hoo dot com > * */ -public class Maxa extends MultiOperandNumericFunction { +public final class Maxa extends MultiOperandNumericFunction { private static final ValueEvalToNumericXlator DEFAULT_NUM_XLATOR = new ValueEvalToNumericXlator((short) ( ValueEvalToNumericXlator.BOOL_IS_PARSED | ValueEvalToNumericXlator.REF_BOOL_IS_PARSED - | ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED | ValueEvalToNumericXlator.STRING_IS_PARSED //| ValueEvalToNumericXlator.REF_STRING_IS_PARSED //| ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Mid.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Mid.java index d6c4399ae..7f30aa4ce 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Mid.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Mid.java @@ -1,99 +1,92 @@ /* -* Licensed to the Apache Software Foundation (ASF) under one or more -* contributor license agreements. See the NOTICE file distributed with -* this work for additional information regarding copyright ownership. -* The ASF licenses this file to You under the Apache License, Version 2.0 -* (the "License"); you may not use this file except in compliance with -* the License. You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ -/* - * Created on May 15, 2005 + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ + package org.apache.poi.hssf.record.formula.functions; import org.apache.poi.hssf.record.formula.eval.BlankEval; import org.apache.poi.hssf.record.formula.eval.ErrorEval; import org.apache.poi.hssf.record.formula.eval.Eval; -import org.apache.poi.hssf.record.formula.eval.NumericValueEval; +import org.apache.poi.hssf.record.formula.eval.EvaluationException; +import org.apache.poi.hssf.record.formula.eval.OperandResolver; import org.apache.poi.hssf.record.formula.eval.StringEval; -import org.apache.poi.hssf.record.formula.eval.StringValueEval; import org.apache.poi.hssf.record.formula.eval.ValueEval; /** - * An implementation of the MID function: - * Returns a specific number of characters from a text string, - * starting at the position you specify, based on the number - * of characters you specify. + * An implementation of the MID function
    MID returns a specific number of + * characters from a text string, starting at the specified position.

    + * + * Syntax:
    MID(text, start_num, + * num_chars)
    + * * @author Manda Wilson < wilson at c bio dot msk cc dot org > */ -public class Mid extends TextFunction { +public class Mid implements Function { /** - * Returns a specific number of characters from a text string, - * starting at the position you specify, based on the number - * of characters you specify. + * Returns a specific number of characters from a text string, starting at + * the position you specify, based on the number of characters you specify. * * @see org.apache.poi.hssf.record.formula.eval.Eval */ - public Eval evaluate(Eval[] operands, int srcCellRow, short srcCellCol) { - Eval retval = null; - String str = null; - int startNum = 0; - int numChars = 0; - - switch (operands.length) { - default: - retval = ErrorEval.VALUE_INVALID; - case 3: - // first operand is text string containing characters to extract - // second operand is position of first character to extract - // third operand is the number of characters to return - ValueEval firstveval = singleOperandEvaluate(operands[0], srcCellRow, srcCellCol); - ValueEval secondveval = singleOperandEvaluate(operands[1], srcCellRow, srcCellCol); - ValueEval thirdveval = singleOperandEvaluate(operands[2], srcCellRow, srcCellCol); - if (firstveval instanceof StringValueEval - && secondveval instanceof NumericValueEval - && thirdveval instanceof NumericValueEval) { - - StringValueEval strEval = (StringValueEval) firstveval; - str = strEval.getStringValue(); - - NumericValueEval startNumEval = (NumericValueEval) secondveval; - // NOTE: it is safe to cast to int here - // because in Excel =MID("test", 1, 1.7) returns t - // so 1.7 must be truncated to 1 - // and =MID("test", 1.9, 2) returns te - // so 1.9 must be truncated to 1 - startNum = (int) startNumEval.getNumberValue(); - - NumericValueEval numCharsEval = (NumericValueEval) thirdveval; - numChars = (int) numCharsEval.getNumberValue(); - - } else { - retval = ErrorEval.VALUE_INVALID; - } - } - - if (retval == null) { - if (startNum < 1 || numChars < 0) { - retval = ErrorEval.VALUE_INVALID; - } else if (startNum > str.length() || numChars == 0) { - retval = BlankEval.INSTANCE; - } else if (startNum + numChars > str.length()) { - retval = new StringEval(str.substring(startNum - 1)); - } else { - retval = new StringEval(str.substring(startNum - 1, numChars)); - } - } - return retval; - } + public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) { + if (args.length != 3) { + return ErrorEval.VALUE_INVALID; + } -} + String text; + int startIx; // zero based + int numChars; + + try { + ValueEval evText = OperandResolver.getSingleValue(args[0], srcCellRow, srcCellCol); + text = OperandResolver.coerceValueToString(evText); + int startCharNum = evaluateNumberArg(args[1], srcCellRow, srcCellCol); + numChars = evaluateNumberArg(args[2], srcCellRow, srcCellCol); + startIx = startCharNum - 1; // convert to zero-based + } catch (EvaluationException e) { + return e.getErrorEval(); + } + + int len = text.length(); + if (startIx < 0) { + return ErrorEval.VALUE_INVALID; + } + if (numChars < 0) { + return ErrorEval.VALUE_INVALID; + } + if (numChars < 0 || startIx > len) { + return new StringEval(""); + } + int endIx = startIx + numChars; + if (endIx > len) { + endIx = len; + } + String result = text.substring(startIx, endIx); + return new StringEval(result); + + } + + private static int evaluateNumberArg(Eval arg, int srcCellRow, short srcCellCol) throws EvaluationException { + ValueEval ev = OperandResolver.getSingleValue(arg, srcCellRow, srcCellCol); + if (ev instanceof BlankEval) { + // Note - for start_num arg, blank causes error(#VALUE!), + // but for num_chars causes empty string to be returned. + return 0; + } + + return OperandResolver.coerceValueToInt(ev); + } +} \ No newline at end of file diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Mina.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Mina.java index a998a870f..21ba47b56 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Mina.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Mina.java @@ -14,10 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on May 15, 2005 - * - */ + package org.apache.poi.hssf.record.formula.functions; import org.apache.poi.hssf.record.formula.eval.ErrorEval; @@ -35,7 +32,6 @@ public class Mina extends MultiOperandNumericFunction { new ValueEvalToNumericXlator((short) ( ValueEvalToNumericXlator.BOOL_IS_PARSED | ValueEvalToNumericXlator.REF_BOOL_IS_PARSED - | ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED | ValueEvalToNumericXlator.STRING_IS_PARSED //| ValueEvalToNumericXlator.REF_STRING_IS_PARSED //| ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/MultiOperandNumericFunction.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/MultiOperandNumericFunction.java index 2840c8988..0e7cce217 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/MultiOperandNumericFunction.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/MultiOperandNumericFunction.java @@ -14,10 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on May 22, 2005 - * - */ + package org.apache.poi.hssf.record.formula.functions; import org.apache.poi.hssf.record.formula.eval.AreaEval; @@ -36,32 +33,52 @@ import org.apache.poi.hssf.record.formula.eval.ValueEvalToNumericXlator; * where the order of operands does not matter */ public abstract class MultiOperandNumericFunction extends NumericFunction { + static final double[] EMPTY_DOUBLE_ARRAY = { }; + + private static class DoubleList { + private double[] _array; + private int _count; - private static final ValueEvalToNumericXlator DEFAULT_NUM_XLATOR = - new ValueEvalToNumericXlator((short) ( - ValueEvalToNumericXlator.BOOL_IS_PARSED - | ValueEvalToNumericXlator.REF_BOOL_IS_PARSED - | ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED - //| ValueEvalToNumericXlator.STRING_IS_PARSED - | ValueEvalToNumericXlator.REF_STRING_IS_PARSED - | ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED - //| ValueEvalToNumericXlator.STRING_TO_BOOL_IS_PARSED - //| ValueEvalToNumericXlator.REF_STRING_TO_BOOL_IS_PARSED - //| ValueEvalToNumericXlator.STRING_IS_INVALID_VALUE - //| ValueEvalToNumericXlator.REF_STRING_IS_INVALID_VALUE - )); + public DoubleList() { + _array = new double[8]; + _count = 0; + } + + public double[] toArray() { + if(_count < 1) { + return EMPTY_DOUBLE_ARRAY; + } + double[] result = new double[_count]; + System.arraycopy(_array, 0, result, 0, _count); + return result; + } + + public void add(double[] values) { + int addLen = values.length; + ensureCapacity(_count + addLen); + System.arraycopy(values, 0, _array, _count, addLen); + _count += addLen; + } + + private void ensureCapacity(int reqSize) { + if(reqSize > _array.length) { + int newSize = reqSize * 3 / 2; // grow with 50% extra + double[] newArr = new double[newSize]; + System.arraycopy(_array, 0, newArr, 0, _count); + _array = newArr; + } + } + + public void add(double value) { + ensureCapacity(_count + 1); + _array[_count] = value; + _count++; + } + } private static final int DEFAULT_MAX_NUM_OPERANDS = 30; - /** - * this is the default impl for the factory method getXlator - * of the super class NumericFunction. Subclasses can override this method - * if they desire to return a different ValueEvalToNumericXlator instance - * than the default. - */ - protected ValueEvalToNumericXlator getXlator() { - return DEFAULT_NUM_XLATOR; - } + protected abstract ValueEvalToNumericXlator getXlator(); /** * Maximum number of operands accepted by this function. @@ -76,40 +93,26 @@ public abstract class MultiOperandNumericFunction extends NumericFunction { * from among the list of operands. Blanks and Blank equivalent cells * are ignored. Error operands or cells containing operands of type * that are considered invalid and would result in #VALUE! error in - * excel cause this function to return null. + * excel cause this function to return null. * * @param operands * @param srcRow * @param srcCol */ protected double[] getNumberArray(Eval[] operands, int srcRow, short srcCol) { - double[] retval = new double[30]; - int count = 0; - - outer: do { // goto simulator loop - if (operands.length > getMaxNumOperands()) { - break outer; - } - else { - for (int i=0, iSize=operands.length; i getMaxNumOperands()) { + return null; } + DoubleList retval = new DoubleList(); - return retval; + for (int i=0, iSize=operands.length; i indicate to calling subclass that error occurred - break; + return null; // indicate to calling subclass that error occurred } } - } - else { // for ValueEvals other than AreaEval - retval = new double[1]; - ValueEval ve = singleOperandEvaluate(operand, srcRow, srcCol); - - if (ve instanceof NumericValueEval) { - NumericValueEval nve = (NumericValueEval) ve; - retval = putInArray(retval, count++, nve.getNumberValue()); - } - else if (ve instanceof BlankEval) {} // ignore operand - else { - retval = null; // null => indicate to calling subclass that error occurred - } + return retval.toArray(); } - if (retval != null && retval.length >= 1) { - double[] temp = retval; - retval = new double[count]; - System.arraycopy(temp, 0, retval, 0, count); + // for ValueEvals other than AreaEval + ValueEval ve = singleOperandEvaluate(operand, srcRow, srcCol); + + if (ve instanceof NumericValueEval) { + NumericValueEval nve = (NumericValueEval) ve; + return new double[] { nve.getNumberValue(), }; } - return retval; + if (ve instanceof BlankEval) { + // ignore blanks + return EMPTY_DOUBLE_ARRAY; + } + return null; } /** - * puts d at position pos in array arr. If pos is greater than arr, the - * array is dynamically resized (using a simple doubling rule). - * @param arr - * @param pos - * @param d + * Ensures that a two dimensional array has all sub-arrays present and the same length + * @return false if any sub-array is missing, or is of different length */ - private static double[] putInArray(double[] arr, int pos, double d) { - double[] tarr = arr; - while (pos >= arr.length) { - arr = new double[arr.length << 1]; - } - if (tarr.length != arr.length) { - System.arraycopy(tarr, 0, arr, 0, tarr.length); - } - arr[pos] = d; - return arr; - } - - private static double[] putInArray(double[] arr, int pos, double[] d) { - double[] tarr = arr; - while (pos+d.length >= arr.length) { - arr = new double[arr.length << 1]; - } - if (tarr.length != arr.length) { - System.arraycopy(tarr, 0, arr, 0, tarr.length); - } - for (int i=0, iSize=d.length; i 0) { - if (values[0] == null) - break outer; - int len = values[0].length; - for (int i=1, iSize=values.length; i + * + * OFFSET returns an area reference that is a specified number of rows and columns from a + * reference cell or area.

    + * + * Syntax:
    + * OFFSET(reference, rows, cols, height, width)

    + * reference is the base reference.
    + * rows is the number of rows up or down from the base reference.
    + * cols is the number of columns left or right from the base reference.
    + * height (default same height as base reference) is the row count for the returned area reference.
    + * width (default same width as base reference) is the column count for the returned area reference.
    + * + * @author Josh Micich + */ +public final class Offset implements FreeRefFunction { + // These values are specific to BIFF8 + private static final int LAST_VALID_ROW_INDEX = 0xFFFF; + private static final int LAST_VALID_COLUMN_INDEX = 0xFF; + + /** + * Exceptions are used within this class to help simplify flow control when error conditions + * are encountered + */ + private static final class EvalEx extends Exception { + private final ErrorEval _error; + + public EvalEx(ErrorEval error) { + _error = error; + } + public ErrorEval getError() { + return _error; + } + } + + /** + * A one dimensional base + offset. Represents either a row range or a column range. + * Two instances of this class together specify an area range. + */ + /* package */ static final class LinearOffsetRange { + + private final int _offset; + private final int _length; + + public LinearOffsetRange(int offset, int length) { + if(length == 0) { + // handled that condition much earlier + throw new RuntimeException("length may not be zero"); + } + _offset = offset; + _length = length; + } + + public short getFirstIndex() { + return (short) _offset; + } + public short getLastIndex() { + return (short) (_offset + _length - 1); + } + /** + * Moves the range by the specified translation amount.

    + * + * This method also 'normalises' the range: Excel specifies that the width and height + * parameters (length field here) cannot be negative. However, OFFSET() does produce + * sensible results in these cases. That behavior is replicated here.

    + * + * @param translationAmount may be zero negative or positive + * + * @return the equivalent LinearOffsetRange with a positive length, moved by the + * specified translationAmount. + */ + public LinearOffsetRange normaliseAndTranslate(int translationAmount) { + if (_length > 0) { + if(translationAmount == 0) { + return this; + } + return new LinearOffsetRange(translationAmount + _offset, _length); + } + return new LinearOffsetRange(translationAmount + _offset + _length + 1, -_length); + } + + public boolean isOutOfBounds(int lowValidIx, int highValidIx) { + if(_offset < lowValidIx) { + return true; + } + if(getLastIndex() > highValidIx) { + return true; + } + return false; + } + public String toString() { + StringBuffer sb = new StringBuffer(64); + sb.append(getClass().getName()).append(" ["); + sb.append(_offset).append("...").append(getLastIndex()); + sb.append("]"); + return sb.toString(); + } + } + + + /** + * Encapsulates either an area or cell reference which may be 2d or 3d. + */ + private static final class BaseRef { + private static final int INVALID_SHEET_INDEX = -1; + private final int _firstRowIndex; + private final int _firstColumnIndex; + private final int _width; + private final int _height; + private final int _externalSheetIndex; + + public BaseRef(RefEval re) { + _firstRowIndex = re.getRow(); + _firstColumnIndex = re.getColumn(); + _height = 1; + _width = 1; + if (re instanceof Ref3DEval) { + Ref3DEval r3e = (Ref3DEval) re; + _externalSheetIndex = r3e.getExternSheetIndex(); + } else { + _externalSheetIndex = INVALID_SHEET_INDEX; + } + } + + public BaseRef(AreaEval ae) { + _firstRowIndex = ae.getFirstRow(); + _firstColumnIndex = ae.getFirstColumn(); + _height = ae.getLastRow() - ae.getFirstRow() + 1; + _width = ae.getLastColumn() - ae.getFirstColumn() + 1; + if (ae instanceof Area3DEval) { + Area3DEval a3e = (Area3DEval) ae; + _externalSheetIndex = a3e.getExternSheetIndex(); + } else { + _externalSheetIndex = INVALID_SHEET_INDEX; + } + } + + public int getWidth() { + return _width; + } + + public int getHeight() { + return _height; + } + + public int getFirstRowIndex() { + return _firstRowIndex; + } + + public int getFirstColumnIndex() { + return _firstColumnIndex; + } + + public boolean isIs3d() { + return _externalSheetIndex > 0; + } + + public short getExternalSheetIndex() { + if(_externalSheetIndex < 0) { + throw new IllegalStateException("external sheet index only available for 3d refs"); + } + return (short) _externalSheetIndex; + } + + } + + public ValueEval evaluate(Eval[] args, int srcCellRow, short srcCellCol, HSSFWorkbook workbook, HSSFSheet sheet) { + + if(args.length < 3 || args.length > 5) { + return ErrorEval.VALUE_INVALID; + } + + + try { + BaseRef baseRef = evaluateBaseRef(args[0]); + int rowOffset = evaluateIntArg(args[1], srcCellRow, srcCellCol); + int columnOffset = evaluateIntArg(args[2], srcCellRow, srcCellCol); + int height = baseRef.getHeight(); + int width = baseRef.getWidth(); + switch(args.length) { + case 5: + width = evaluateIntArg(args[4], srcCellRow, srcCellCol); + case 4: + height = evaluateIntArg(args[3], srcCellRow, srcCellCol); + } + // Zero height or width raises #REF! error + if(height == 0 || width == 0) { + return ErrorEval.REF_INVALID; + } + LinearOffsetRange rowOffsetRange = new LinearOffsetRange(rowOffset, height); + LinearOffsetRange colOffsetRange = new LinearOffsetRange(columnOffset, width); + return createOffset(baseRef, rowOffsetRange, colOffsetRange, workbook, sheet); + } catch (EvalEx e) { + return e.getError(); + } + } + + + private static AreaEval createOffset(BaseRef baseRef, + LinearOffsetRange rowOffsetRange, LinearOffsetRange colOffsetRange, + HSSFWorkbook workbook, HSSFSheet sheet) throws EvalEx { + + LinearOffsetRange rows = rowOffsetRange.normaliseAndTranslate(baseRef.getFirstRowIndex()); + LinearOffsetRange cols = colOffsetRange.normaliseAndTranslate(baseRef.getFirstColumnIndex()); + + if(rows.isOutOfBounds(0, LAST_VALID_ROW_INDEX)) { + throw new EvalEx(ErrorEval.REF_INVALID); + } + if(cols.isOutOfBounds(0, LAST_VALID_COLUMN_INDEX)) { + throw new EvalEx(ErrorEval.REF_INVALID); + } + if(baseRef.isIs3d()) { + Area3DPtg a3dp = new Area3DPtg(rows.getFirstIndex(), rows.getLastIndex(), + cols.getFirstIndex(), cols.getLastIndex(), + false, false, false, false, + baseRef.getExternalSheetIndex()); + return HSSFFormulaEvaluator.evaluateArea3dPtg(workbook, a3dp); + } + + AreaPtg ap = new AreaPtg(rows.getFirstIndex(), rows.getLastIndex(), + cols.getFirstIndex(), cols.getLastIndex(), + false, false, false, false); + return HSSFFormulaEvaluator.evaluateAreaPtg(sheet, workbook, ap); + } + + + private static BaseRef evaluateBaseRef(Eval eval) throws EvalEx { + + if(eval instanceof RefEval) { + return new BaseRef((RefEval)eval); + } + if(eval instanceof AreaEval) { + return new BaseRef((AreaEval)eval); + } + if (eval instanceof ErrorEval) { + throw new EvalEx((ErrorEval) eval); + } + throw new EvalEx(ErrorEval.VALUE_INVALID); + } + + + /** + * OFFSET's numeric arguments (2..5) have similar processing rules + */ + private static int evaluateIntArg(Eval eval, int srcCellRow, short srcCellCol) throws EvalEx { + + double d = evaluateDoubleArg(eval, srcCellRow, srcCellCol); + return convertDoubleToInt(d); + } + + /** + * Fractional values are silently truncated by Excel. + * Truncation is toward negative infinity. + */ + /* package */ static int convertDoubleToInt(double d) { + // Note - the standard java type conversion from double to int truncates toward zero. + // but Math.floor() truncates toward negative infinity + return (int)Math.floor(d); + } + + + private static double evaluateDoubleArg(Eval eval, int srcCellRow, short srcCellCol) throws EvalEx { + ValueEval ve = evaluateSingleValue(eval, srcCellRow, srcCellCol); + + if (ve instanceof NumericValueEval) { + return ((NumericValueEval) ve).getNumberValue(); + } + if (ve instanceof StringEval) { + StringEval se = (StringEval) ve; + Double d = parseDouble(se.getStringValue()); + if(d == null) { + throw new EvalEx(ErrorEval.VALUE_INVALID); + } + return d.doubleValue(); + } + if (ve instanceof BoolEval) { + // in the context of OFFSET, booleans resolve to 0 and 1. + if(((BoolEval) ve).getBooleanValue()) { + return 1; + } + return 0; + } + throw new RuntimeException("Unexpected eval type (" + ve.getClass().getName() + ")"); + } + + private static Double parseDouble(String s) { + // TODO - find a home for this method + // TODO - support various number formats: sign char, dollars, commas + // OFFSET and COUNTIF seem to handle these + return Countif.parseDouble(s); + } + + private static ValueEval evaluateSingleValue(Eval eval, int srcCellRow, short srcCellCol) throws EvalEx { + if(eval instanceof RefEval) { + return ((RefEval)eval).getInnerValueEval(); + } + if(eval instanceof AreaEval) { + return chooseSingleElementFromArea((AreaEval)eval, srcCellRow, srcCellCol); + } + if (eval instanceof ValueEval) { + return (ValueEval) eval; + } + throw new RuntimeException("Unexpected eval type (" + eval.getClass().getName() + ")"); + } + + // TODO - this code seems to get repeated a bit + private static ValueEval chooseSingleElementFromArea(AreaEval ae, int srcCellRow, short srcCellCol) throws EvalEx { + if (ae.isColumn()) { + if (ae.isRow()) { + return ae.getValues()[0]; + } + if (!ae.containsRow(srcCellRow)) { + throw new EvalEx(ErrorEval.VALUE_INVALID); + } + return ae.getValueAt(srcCellRow, ae.getFirstColumn()); + } + if (!ae.isRow()) { + throw new EvalEx(ErrorEval.VALUE_INVALID); + } + if (!ae.containsColumn(srcCellCol)) { + throw new EvalEx(ErrorEval.VALUE_INVALID); + } + return ae.getValueAt(ae.getFirstRow(), srcCellCol); + } } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Rounddown.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Rounddown.java index ab446c9f2..13522294f 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Rounddown.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Rounddown.java @@ -40,6 +40,9 @@ public class Rounddown extends NumericFunction { break; case 2: ValueEval ve = singleOperandEvaluate(operands[0], srcRow, srcCol); + if(ve instanceof ErrorEval) { + return ve; + } if (ve instanceof NumericValueEval) { NumericValueEval ne = (NumericValueEval) ve; d0 = ne.getNumberValue(); diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Roundup.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Roundup.java index 3d8cc1ae3..4dae76d98 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Roundup.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Roundup.java @@ -40,6 +40,9 @@ public class Roundup extends NumericFunction { break; case 2: ValueEval ve = singleOperandEvaluate(operands[0], srcRow, srcCol); + if(ve instanceof ErrorEval) { + return ve; + } if (ve instanceof NumericValueEval) { NumericValueEval ne = (NumericValueEval) ve; d0 = ne.getNumberValue(); diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Rows.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Rows.java index 6a4eb8edb..aabffab2f 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Rows.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Rows.java @@ -25,7 +25,7 @@ import org.apache.poi.hssf.record.formula.eval.NumberEval; import org.apache.poi.hssf.record.formula.eval.RefEval; /** - * Implementation for Excel COLUMNS function. + * Implementation for Excel ROWS function. * * @author Josh Micich */ diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Stdev.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Stdev.java index fef7e0346..7995e66c3 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Stdev.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Stdev.java @@ -33,8 +33,8 @@ import org.apache.poi.hssf.record.formula.eval.ValueEvalToNumericXlator; public class Stdev extends MultiOperandNumericFunction { private static final ValueEvalToNumericXlator DEFAULT_NUM_XLATOR = - new ValueEvalToNumericXlator((short) (0 - // ValueEvalToNumericXlator.BOOL_IS_PARSED + new ValueEvalToNumericXlator((short) ( + ValueEvalToNumericXlator.BOOL_IS_PARSED //| ValueEvalToNumericXlator.REF_BOOL_IS_PARSED //| ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED | ValueEvalToNumericXlator.STRING_IS_PARSED @@ -44,7 +44,6 @@ public class Stdev extends MultiOperandNumericFunction { //| ValueEvalToNumericXlator.REF_STRING_TO_BOOL_IS_PARSED //| ValueEvalToNumericXlator.STRING_IS_INVALID_VALUE //| ValueEvalToNumericXlator.REF_STRING_IS_INVALID_VALUE - | ValueEvalToNumericXlator.EVALUATED_REF_BLANK_IS_PARSED )); /** diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumproduct.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumproduct.java index 12fa5d7bd..9f6eafa4d 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumproduct.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumproduct.java @@ -14,16 +14,228 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on May 15, 2005 - * - */ + + package org.apache.poi.hssf.record.formula.functions; +import org.apache.poi.hssf.record.formula.eval.AreaEval; +import org.apache.poi.hssf.record.formula.eval.BlankEval; +import org.apache.poi.hssf.record.formula.eval.ErrorEval; +import org.apache.poi.hssf.record.formula.eval.Eval; +import org.apache.poi.hssf.record.formula.eval.NumberEval; +import org.apache.poi.hssf.record.formula.eval.NumericValueEval; +import org.apache.poi.hssf.record.formula.eval.RefEval; +import org.apache.poi.hssf.record.formula.eval.StringEval; +import org.apache.poi.hssf.record.formula.eval.ValueEval; + /** - * @author Amol S. Deshmukh < amolweb at ya hoo dot com > - * + * Implementation for the Excel function SUMPRODUCT

    + * + * Syntax :
    + * SUMPRODUCT ( array1[, array2[, array3[, ...]]]) + * + * + *
    array1, ... arrayN  typically area references, + * possibly cell references or scalar values

    + * + * Let An(i,j) represent the element in the ith row jth column + * of the nth array
    + * Assuming each array has the same dimensions (W, H), the result is defined as:
    + * SUMPRODUCT = Σi: 1..H   + * (  Σj: 1..W   + * (  Πn: 1..N + * An(i,j)  + * )  + * ) + * + * @author Josh Micich */ -public class Sumproduct extends NotImplementedFunction { +public final class Sumproduct implements Function { + + private static final class EvalEx extends Exception { + private final ErrorEval _error; + + public EvalEx(ErrorEval error) { + _error = error; + } + public ErrorEval getError() { + return _error; + } + } + + public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) { + + int maxN = args.length; + + if(maxN < 1) { + return ErrorEval.VALUE_INVALID; + } + Eval firstArg = args[0]; + try { + if(firstArg instanceof NumericValueEval) { + return evaluateSingleProduct(args); + } + if(firstArg instanceof RefEval) { + return evaluateSingleProduct(args); + } + if(firstArg instanceof AreaEval) { + AreaEval ae = (AreaEval) firstArg; + if(ae.isRow() && ae.isColumn()) { + return evaluateSingleProduct(args); + } + return evaluateAreaSumProduct(args); + } + } catch (EvalEx e) { + return e.getError(); + } + throw new RuntimeException("Invalid arg type for SUMPRODUCT: (" + + firstArg.getClass().getName() + ")"); + } + + private Eval evaluateSingleProduct(Eval[] evalArgs) throws EvalEx { + int maxN = evalArgs.length; + + double term = 1D; + for(int n=0; ndouble value for the specified ValueEval. + * @param isScalarProduct false for SUMPRODUCTs over area refs. + * @throws EvalEx if ve represents an error value. + *

    + * Note - string values and empty cells are interpreted differently depending on + * isScalarProduct. For scalar products, if any term is blank or a string, the + * error (#VALUE!) is raised. For area (sum)products, if any term is blank or a string, the + * result is zero. + */ + private static double getProductTerm(ValueEval ve, boolean isScalarProduct) throws EvalEx { + + if(ve instanceof BlankEval || ve == null) { + // TODO - shouldn't BlankEval.INSTANCE be used always instead of null? + // null seems to occur when the blank cell is part of an area ref (but not reliably) + if(isScalarProduct) { + throw new EvalEx(ErrorEval.VALUE_INVALID); + } + return 0; + } + + if(ve instanceof ErrorEval) { + throw new EvalEx((ErrorEval)ve); + } + if(ve instanceof StringEval) { + if(isScalarProduct) { + throw new EvalEx(ErrorEval.VALUE_INVALID); + } + // Note for area SUMPRODUCTs, string values are interpreted as zero + // even if they would parse as valid numeric values + return 0; + } + if(ve instanceof NumericValueEval) { + NumericValueEval nve = (NumericValueEval) ve; + return nve.getNumberValue(); + } + throw new RuntimeException("Unexpected value eval class (" + + ve.getClass().getName() + ")"); + } } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumsq.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumsq.java index f4e1959be..b74b4161a 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumsq.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumsq.java @@ -33,18 +33,18 @@ import org.apache.poi.hssf.record.formula.eval.ValueEvalToNumericXlator; public class Sumsq extends MultiOperandNumericFunction { private static final ValueEvalToNumericXlator DEFAULT_NUM_XLATOR = new ValueEvalToNumericXlator((short) ( - // ValueEvalToNumericXlator.BOOL_IS_PARSED + ValueEvalToNumericXlator.BOOL_IS_PARSED //| ValueEvalToNumericXlator.REF_BOOL_IS_PARSED //| ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED - //| ValueEvalToNumericXlator.STRING_IS_PARSED + | ValueEvalToNumericXlator.STRING_IS_PARSED //| ValueEvalToNumericXlator.REF_STRING_IS_PARSED //| ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED //| ValueEvalToNumericXlator.STRING_TO_BOOL_IS_PARSED //| ValueEvalToNumericXlator.REF_STRING_TO_BOOL_IS_PARSED //| ValueEvalToNumericXlator.STRING_IS_INVALID_VALUE //| ValueEvalToNumericXlator.REF_STRING_IS_INVALID_VALUE - ValueEvalToNumericXlator.REF_BLANK_IS_PARSED - | ValueEvalToNumericXlator.BLANK_IS_PARSED + //| ValueEvalToNumericXlator.REF_BLANK_IS_PARSED + //| ValueEvalToNumericXlator.BLANK_IS_PARSED )); protected ValueEvalToNumericXlator getXlator() { diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumx2my2.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumx2my2.java index 8e3122407..30ad5ec23 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumx2my2.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumx2my2.java @@ -14,50 +14,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on May 15, 2005 - * - */ + package org.apache.poi.hssf.record.formula.functions; -import org.apache.poi.hssf.record.formula.eval.ErrorEval; -import org.apache.poi.hssf.record.formula.eval.Eval; -import org.apache.poi.hssf.record.formula.eval.NumberEval; -import org.apache.poi.hssf.record.formula.eval.ValueEval; - /** + * Implementation of Excel function SUMX2MY2()

    + * + * Calculates the sum of differences of squares in two arrays of the same size.
    + * Syntax:
    + * SUMX2MY2(arrayX, arrayY)

    + * + * result = Σi: 0..n(xi2-yi2) + * * @author Amol S. Deshmukh < amolweb at ya hoo dot com > - * */ -public class Sumx2my2 extends XYNumericFunction { +public final class Sumx2my2 extends XYNumericFunction { - - public Eval evaluate(Eval[] operands, int srcCellRow, short srcCellCol) { - ValueEval retval = null; - double[][] values = null; - - int checkLen = 0; // check to see that all array lengths are equal - switch (operands.length) { - default: - retval = ErrorEval.VALUE_INVALID; - break; - case 2: - values = getValues(operands, srcCellRow, srcCellCol); - if (values==null - || values[X] == null || values[Y] == null - || values[X].length == 0 || values[Y].length == 0 - || values[X].length != values[Y].length) { - retval = ErrorEval.VALUE_INVALID; - } - } - - if (retval == null) { - double d = MathX.sumx2my2(values[X], values[Y]); - retval = (Double.isNaN(d) || Double.isInfinite(d)) - ? (ValueEval) ErrorEval.NUM_ERROR - : new NumberEval(d); - } - - return retval; + protected double evaluate(double[] xArray, double[] yArray) { + return MathX.sumx2my2(xArray, yArray); } } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumx2py2.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumx2py2.java index deb7675a4..dfd730d12 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumx2py2.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumx2py2.java @@ -14,50 +14,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on May 15, 2005 - * - */ + package org.apache.poi.hssf.record.formula.functions; -import org.apache.poi.hssf.record.formula.eval.ErrorEval; -import org.apache.poi.hssf.record.formula.eval.Eval; -import org.apache.poi.hssf.record.formula.eval.NumberEval; -import org.apache.poi.hssf.record.formula.eval.ValueEval; - /** + * Implementation of Excel function SUMX2PY2()

    + * + * Calculates the sum of squares in two arrays of the same size.
    + * Syntax:
    + * SUMX2PY2(arrayX, arrayY)

    + * + * result = Σi: 0..n(xi2+yi2) + * * @author Amol S. Deshmukh < amolweb at ya hoo dot com > - * */ -public class Sumx2py2 extends XYNumericFunction { +public final class Sumx2py2 extends XYNumericFunction { - - public Eval evaluate(Eval[] operands, int srcCellRow, short srcCellCol) { - ValueEval retval = null; - double[][] values = null; - - int checkLen = 0; // check to see that all array lengths are equal - switch (operands.length) { - default: - retval = ErrorEval.VALUE_INVALID; - break; - case 2: - values = getValues(operands, srcCellRow, srcCellCol); - if (values==null - || values[X] == null || values[Y] == null - || values[X].length == 0 || values[Y].length == 0 - || values[X].length != values[Y].length) { - retval = ErrorEval.VALUE_INVALID; - } - } - - if (retval == null) { - double d = MathX.sumx2py2(values[X], values[Y]); - retval = (Double.isNaN(d) || Double.isInfinite(d)) - ? (ValueEval) ErrorEval.NUM_ERROR - : new NumberEval(d); - } - - return retval; + protected double evaluate(double[] xArray, double[] yArray) { + return MathX.sumx2py2(xArray, yArray); } } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumxmy2.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumxmy2.java index c62a0b762..a1b2fec9b 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumxmy2.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumxmy2.java @@ -14,50 +14,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on May 15, 2005 - * - */ + package org.apache.poi.hssf.record.formula.functions; -import org.apache.poi.hssf.record.formula.eval.ErrorEval; -import org.apache.poi.hssf.record.formula.eval.Eval; -import org.apache.poi.hssf.record.formula.eval.NumberEval; -import org.apache.poi.hssf.record.formula.eval.ValueEval; - /** + * Implementation of Excel function SUMXMY2()

    + * + * Calculates the sum of squares of differences between two arrays of the same size.
    + * Syntax:
    + * SUMXMY2(arrayX, arrayY)

    + * + * result = Σi: 0..n(xi-yi)2 + * * @author Amol S. Deshmukh < amolweb at ya hoo dot com > - * */ -public class Sumxmy2 extends XYNumericFunction { +public final class Sumxmy2 extends XYNumericFunction { - - public Eval evaluate(Eval[] operands, int srcCellRow, short srcCellCol) { - ValueEval retval = null; - double[][] values = null; - - int checkLen = 0; // check to see that all array lengths are equal - switch (operands.length) { - default: - retval = ErrorEval.VALUE_INVALID; - break; - case 2: - values = getValues(operands, srcCellRow, srcCellCol); - if (values==null - || values[X] == null || values[Y] == null - || values[X].length == 0 || values[Y].length == 0 - || values[X].length != values[Y].length) { - retval = ErrorEval.VALUE_INVALID; - } - } - - if (retval == null) { - double d = MathX.sumxmy2(values[X], values[Y]); - retval = (Double.isNaN(d) || Double.isInfinite(d)) - ? (ValueEval) ErrorEval.NUM_ERROR - : new NumberEval(d); - } - - return retval; + protected double evaluate(double[] xArray, double[] yArray) { + return MathX.sumxmy2(xArray, yArray); } } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/T.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/T.java index 686c40b62..b322cd985 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/T.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/T.java @@ -22,28 +22,34 @@ package org.apache.poi.hssf.record.formula.functions; import org.apache.poi.hssf.record.formula.eval.ErrorEval; import org.apache.poi.hssf.record.formula.eval.Eval; +import org.apache.poi.hssf.record.formula.eval.RefEval; import org.apache.poi.hssf.record.formula.eval.StringEval; -import org.apache.poi.hssf.record.formula.eval.ValueEval; -public class T implements Function { - - +public final class T implements Function { - public Eval evaluate(Eval[] operands, int srcCellRow, short srcCellCol) { - ValueEval retval = null; - switch (operands.length) { - default: - retval = ErrorEval.VALUE_INVALID; - break; - case 1: - if (operands[0] instanceof StringEval - || operands[0] instanceof ErrorEval) { - retval = (ValueEval) operands[0]; - } - else if (operands[0] instanceof ErrorEval) { - retval = StringEval.EMPTY_INSTANCE; - } + public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) { + switch (args.length) { + default: + return ErrorEval.VALUE_INVALID; + case 1: + break; } - return retval; + Eval arg = args[0]; + if (arg instanceof RefEval) { + RefEval re = (RefEval) arg; + arg = re.getInnerValueEval(); + } + + if (arg instanceof StringEval) { + // Text values are returned unmodified + return arg; + } + + if (arg instanceof ErrorEval) { + // Error values also returned unmodified + return arg; + } + // for all other argument types the result is empty string + return StringEval.EMPTY_INSTANCE; } } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Trim.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Trim.java index 5e9d91c7c..87e29ee34 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Trim.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Trim.java @@ -16,12 +16,11 @@ */ package org.apache.poi.hssf.record.formula.functions; -import org.apache.poi.hssf.record.formula.eval.BlankEval; import org.apache.poi.hssf.record.formula.eval.ErrorEval; import org.apache.poi.hssf.record.formula.eval.Eval; -import org.apache.poi.hssf.record.formula.eval.NumberEval; +import org.apache.poi.hssf.record.formula.eval.EvaluationException; +import org.apache.poi.hssf.record.formula.eval.OperandResolver; import org.apache.poi.hssf.record.formula.eval.StringEval; -import org.apache.poi.hssf.record.formula.eval.StringValueEval; import org.apache.poi.hssf.record.formula.eval.ValueEval; /** @@ -30,46 +29,25 @@ import org.apache.poi.hssf.record.formula.eval.ValueEval; * value is string. * @author Manda Wilson < wilson at c bio dot msk cc dot org > */ -public class Trim extends TextFunction { +public final class Trim extends TextFunction { - /** - * Removes leading and trailing spaces from value if evaluated - * operand value is string. - * Returns StringEval only if evaluated operand is of type string - * (and is not blank or null) or number. If evaluated operand is - * of type string and is blank or null, or if evaluated operand is - * of type blank, returns BlankEval. Otherwise returns ErrorEval. - * - * @see org.apache.poi.hssf.record.formula.eval.Eval - */ - public Eval evaluate(Eval[] operands, int srcCellRow, short srcCellCol) { - Eval retval = ErrorEval.VALUE_INVALID; - String str = null; - - switch (operands.length) { - default: - break; - case 1: - ValueEval veval = singleOperandEvaluate(operands[0], srcCellRow, srcCellCol); - if (veval instanceof StringValueEval) { - StringValueEval sve = (StringValueEval) veval; - str = sve.getStringValue(); - if (str == null || str.trim().equals("")) { - return BlankEval.INSTANCE; - } - } - else if (veval instanceof NumberEval) { - NumberEval neval = (NumberEval) veval; - str = neval.getStringValue(); - } - else if (veval instanceof BlankEval) { - return BlankEval.INSTANCE; - } - } - - if (str != null) { - retval = new StringEval(str.trim()); - } - return retval; - } + public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) { + + if(args.length != 1) { + return ErrorEval.VALUE_INVALID; + } + + try { + ValueEval veval = OperandResolver.getSingleValue(args[0], srcCellRow, srcCellCol); + + String str = OperandResolver.coerceValueToString(veval); + str = str.trim(); + if(str.length() < 1) { + return StringEval.EMPTY_INSTANCE; + } + return new StringEval(str); + } catch (EvaluationException e) { + return e.getErrorEval(); + } + } } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Vlookup.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Vlookup.java index ad8b88daf..7d27491df 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Vlookup.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Vlookup.java @@ -1,25 +1,123 @@ -/* -* Licensed to the Apache Software Foundation (ASF) under one or more -* contributor license agreements. See the NOTICE file distributed with -* this work for additional information regarding copyright ownership. -* The ASF licenses this file to You under the Apache License, Version 2.0 -* (the "License"); you may not use this file except in compliance with -* the License. You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ -/* - * Created on May 15, 2005 - * - */ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + package org.apache.poi.hssf.record.formula.functions; -public class Vlookup extends NotImplementedFunction { +import org.apache.poi.hssf.record.formula.eval.AreaEval; +import org.apache.poi.hssf.record.formula.eval.ErrorEval; +import org.apache.poi.hssf.record.formula.eval.Eval; +import org.apache.poi.hssf.record.formula.eval.EvaluationException; +import org.apache.poi.hssf.record.formula.eval.OperandResolver; +import org.apache.poi.hssf.record.formula.eval.ValueEval; +import org.apache.poi.hssf.record.formula.functions.LookupUtils.ValueVector; +/** + * Implementation of the VLOOKUP() function.

    + * + * VLOOKUP finds a row in a lookup table by the first column value and returns the value from another column. + * + * Syntax:
    + * VLOOKUP(lookup_value, table_array, col_index_num, range_lookup)

    + * + * lookup_value The value to be found in the first column of the table array.
    + * table_array An area reference for the lookup data.
    + * col_index_num a 1 based index specifying which column value of the lookup data will be returned.
    + * range_lookup If TRUE (default), VLOOKUP finds the largest value less than or equal to + * the lookup_value. If FALSE, only exact matches will be considered
    + * + * @author Josh Micich + */ +public final class Vlookup implements Function { + + private static final class ColumnVector implements ValueVector { + private final AreaEval _tableArray; + private final int _size; + private final int _columnAbsoluteIndex; + private final int _firstRowAbsoluteIndex; + + public ColumnVector(AreaEval tableArray, int columnIndex) { + _columnAbsoluteIndex = tableArray.getFirstColumn() + columnIndex; + if(!tableArray.containsColumn((short)_columnAbsoluteIndex)) { + int lastColIx = tableArray.getLastColumn() - tableArray.getFirstColumn(); + throw new IllegalArgumentException("Specified column index (" + columnIndex + + ") is outside the allowed range (0.." + lastColIx + ")"); + } + _tableArray = tableArray; + _size = tableArray.getLastRow() - tableArray.getFirstRow() + 1; + if(_size < 1) { + throw new RuntimeException("bad table array size zero"); + } + _firstRowAbsoluteIndex = tableArray.getFirstRow(); + } + + public ValueEval getItem(int index) { + if(index>_size) { + throw new ArrayIndexOutOfBoundsException("Specified index (" + index + + ") is outside the allowed range (0.." + (_size-1) + ")"); + } + return _tableArray.getValueAt(_firstRowAbsoluteIndex + index, (short)_columnAbsoluteIndex); + } + public int getSize() { + return _size; + } + } + + public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) { + Eval arg3 = null; + switch(args.length) { + case 4: + arg3 = args[3]; // important: assumed array element is never null + case 3: + break; + default: + // wrong number of arguments + return ErrorEval.VALUE_INVALID; + } + try { + // Evaluation order: + // arg0 lookup_value, arg1 table_array, arg3 range_lookup, find lookup value, arg2 col_index, fetch result + ValueEval lookupValue = OperandResolver.getSingleValue(args[0], srcCellRow, srcCellCol); + AreaEval tableArray = LookupUtils.resolveTableArrayArg(args[1]); + boolean isRangeLookup = LookupUtils.resolveRangeLookupArg(arg3, srcCellRow, srcCellCol); + int rowIndex = LookupUtils.lookupIndexOfValue(lookupValue, new ColumnVector(tableArray, 0), isRangeLookup); + ValueEval veColIndex = OperandResolver.getSingleValue(args[2], srcCellRow, srcCellCol); + int colIndex = LookupUtils.resolveRowOrColIndexArg(veColIndex); + ValueVector resultCol = createResultColumnVector(tableArray, colIndex); + return resultCol.getItem(rowIndex); + } catch (EvaluationException e) { + return e.getErrorEval(); + } + } + + + /** + * Returns one column from an AreaEval + * + * @throws EvaluationException (#VALUE!) if colIndex is negative, (#REF!) if colIndex is too high + */ + private ValueVector createResultColumnVector(AreaEval tableArray, int colIndex) throws EvaluationException { + if(colIndex < 0) { + throw EvaluationException.invalidValue(); + } + int nCols = tableArray.getLastColumn() - tableArray.getFirstColumn() + 1; + + if(colIndex >= nCols) { + throw EvaluationException.invalidRef(); + } + return new ColumnVector(tableArray, colIndex); + } } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/XYNumericFunction.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/XYNumericFunction.java index 1e6955ad9..b989c33a2 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/XYNumericFunction.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/XYNumericFunction.java @@ -14,154 +14,151 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on May 29, 2005 - * - */ + package org.apache.poi.hssf.record.formula.functions; import org.apache.poi.hssf.record.formula.eval.AreaEval; import org.apache.poi.hssf.record.formula.eval.ErrorEval; import org.apache.poi.hssf.record.formula.eval.Eval; +import org.apache.poi.hssf.record.formula.eval.EvaluationException; import org.apache.poi.hssf.record.formula.eval.NumberEval; import org.apache.poi.hssf.record.formula.eval.RefEval; import org.apache.poi.hssf.record.formula.eval.ValueEval; -import org.apache.poi.hssf.record.formula.eval.ValueEvalToNumericXlator; /** * @author Amol S. Deshmukh < amolweb at ya hoo dot com > * */ -public abstract class XYNumericFunction extends NumericFunction { +public abstract class XYNumericFunction implements Function { protected static final int X = 0; protected static final int Y = 1; - - private static final ValueEvalToNumericXlator DEFAULT_NUM_XLATOR = - new ValueEvalToNumericXlator((short) ( - ValueEvalToNumericXlator.BOOL_IS_PARSED - | ValueEvalToNumericXlator.REF_BOOL_IS_PARSED - | ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED - //| ValueEvalToNumericXlator.STRING_IS_PARSED - | ValueEvalToNumericXlator.REF_STRING_IS_PARSED - | ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED - //| ValueEvalToNumericXlator.STRING_TO_BOOL_IS_PARSED - //| ValueEvalToNumericXlator.REF_STRING_TO_BOOL_IS_PARSED - //| ValueEvalToNumericXlator.STRING_IS_INVALID_VALUE - //| ValueEvalToNumericXlator.REF_STRING_IS_INVALID_VALUE - )); - - /** - * this is the default impl for the factory method getXlator - * of the super class NumericFunction. Subclasses can override this method - * if they desire to return a different ValueEvalToNumericXlator instance - * than the default. - */ - protected ValueEvalToNumericXlator getXlator() { - return DEFAULT_NUM_XLATOR; - } - protected int getMaxNumOperands() { - return 30; + protected static final class DoubleArrayPair { + + private final double[] _xArray; + private final double[] _yArray; + + public DoubleArrayPair(double[] xArray, double[] yArray) { + _xArray = xArray; + _yArray = yArray; + } + public double[] getXArray() { + return _xArray; + } + public double[] getYArray() { + return _yArray; + } } + + public final Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) { + if(args.length != 2) { + return ErrorEval.VALUE_INVALID; + } + + double[][] values; + try { + values = getValues(args[0], args[1]); + } catch (EvaluationException e) { + return e.getErrorEval(); + } + if (values==null + || values[X] == null || values[Y] == null + || values[X].length == 0 || values[Y].length == 0 + || values[X].length != values[Y].length) { + return ErrorEval.VALUE_INVALID; + } + + double d = evaluate(values[X], values[Y]); + if (Double.isNaN(d) || Double.isInfinite(d)) { + return ErrorEval.NUM_ERROR; + } + return new NumberEval(d); + } + protected abstract double evaluate(double[] xArray, double[] yArray); + /** * Returns a double array that contains values for the numeric cells * from among the list of operands. Blanks and Blank equivalent cells * are ignored. Error operands or cells containing operands of type * that are considered invalid and would result in #VALUE! error in * excel cause this function to return null. - * - * @param xops - * @param yops - * @param srcRow - * @param srcCol */ - protected double[][] getNumberArray(Eval[] xops, Eval[] yops, int srcRow, short srcCol) { - double[][] retval = new double[2][30]; + private static double[][] getNumberArray(Eval[] xops, Eval[] yops) throws EvaluationException { + + // check for errors first: size mismatch, value errors in x, value errors in y + + int nArrayItems = xops.length; + if(nArrayItems != yops.length) { + throw new EvaluationException(ErrorEval.NA); + } + for (int i = 0; i < xops.length; i++) { + Eval eval = xops[i]; + if (eval instanceof ErrorEval) { + throw new EvaluationException((ErrorEval) eval); + } + } + for (int i = 0; i < yops.length; i++) { + Eval eval = yops[i]; + if (eval instanceof ErrorEval) { + throw new EvaluationException((ErrorEval) eval); + } + } + + double[] xResult = new double[nArrayItems]; + double[] yResult = new double[nArrayItems]; + int count = 0; - if (xops.length > getMaxNumOperands() - || yops.length > getMaxNumOperands() - || xops.length != yops.length) { - retval = null; - } - else { - - for (int i=0, iSize=xops.length; i= arr.length) { - arr = new double[arr.length << 2]; - } - if (temp.length != arr.length) - System.arraycopy(temp, 0, arr, 0, temp.length); - return arr; - } - - protected static double[] trimToSize(double[] arr, int len) { + private static double[] trimToSize(double[] arr, int len) { double[] tarr = arr; if (arr.length > len) { tarr = new double[len]; @@ -170,7 +167,7 @@ public abstract class XYNumericFunction extends NumericFunction { return tarr; } - protected static boolean isNumberEval(Eval eval) { + private static boolean isNumberEval(Eval eval) { boolean retval = false; if (eval instanceof NumberEval) { @@ -185,7 +182,7 @@ public abstract class XYNumericFunction extends NumericFunction { return retval; } - protected static double getDoubleValue(Eval eval) { + private static double getDoubleValue(Eval eval) { double retval = 0; if (eval instanceof NumberEval) { NumberEval ne = (NumberEval) eval; diff --git a/src/scratchpad/src/org/apache/poi/hssf/usermodel/EvaluationCycleDetector.java b/src/scratchpad/src/org/apache/poi/hssf/usermodel/EvaluationCycleDetector.java new file mode 100755 index 000000000..90f5807ff --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hssf/usermodel/EvaluationCycleDetector.java @@ -0,0 +1,150 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hssf.usermodel; + +import java.util.ArrayList; +import java.util.List; + +/** + * Instances of this class keep track of multiple dependent cell evaluations due + * to recursive calls to HSSFFormulaEvaluator.internalEvaluate(). + * The main purpose of this class is to detect an attempt to evaluate a cell + * that is already being evaluated. In other words, it detects circular + * references in spreadsheet formulas. + * + * @author Josh Micich + */ +final class EvaluationCycleDetector { + + /** + * Stores the parameters that identify the evaluation of one cell.
    + */ + private static final class CellEvaluationFrame { + + private final HSSFWorkbook _workbook; + private final HSSFSheet _sheet; + private final int _srcRowNum; + private final int _srcColNum; + + public CellEvaluationFrame(HSSFWorkbook workbook, HSSFSheet sheet, int srcRowNum, int srcColNum) { + if (workbook == null) { + throw new IllegalArgumentException("workbook must not be null"); + } + if (sheet == null) { + throw new IllegalArgumentException("sheet must not be null"); + } + _workbook = workbook; + _sheet = sheet; + _srcRowNum = srcRowNum; + _srcColNum = srcColNum; + } + + public boolean equals(Object obj) { + CellEvaluationFrame other = (CellEvaluationFrame) obj; + if (_workbook != other._workbook) { + return false; + } + if (_sheet != other._sheet) { + return false; + } + if (_srcRowNum != other._srcRowNum) { + return false; + } + if (_srcColNum != other._srcColNum) { + return false; + } + return true; + } + + /** + * @return human readable string for debug purposes + */ + public String formatAsString() { + return "R=" + _srcRowNum + " C=" + _srcColNum + " ShIx=" + _workbook.getSheetIndex(_sheet); + } + + public String toString() { + StringBuffer sb = new StringBuffer(64); + sb.append(getClass().getName()).append(" ["); + sb.append(formatAsString()); + sb.append("]"); + return sb.toString(); + } + } + + private final List _evaluationFrames; + + public EvaluationCycleDetector() { + _evaluationFrames = new ArrayList(); + } + + /** + * Notifies this evaluation tracker that evaluation of the specified cell is + * about to start.
    + * + * In the case of a true return code, the caller should + * continue evaluation of the specified cell, and also be sure to call + * endEvaluate() when complete.
    + * + * In the case of a false return code, the caller should + * return an evaluation result of + * ErrorEval.CIRCULAR_REF_ERROR, and not call endEvaluate(). + *
    + * @return true if the specified cell has not been visited yet in the current + * evaluation. false if the specified cell is already being evaluated. + */ + public boolean startEvaluate(HSSFWorkbook workbook, HSSFSheet sheet, int srcRowNum, int srcColNum) { + CellEvaluationFrame cef = new CellEvaluationFrame(workbook, sheet, srcRowNum, srcColNum); + if (_evaluationFrames.contains(cef)) { + return false; + } + _evaluationFrames.add(cef); + return true; + } + + /** + * Notifies this evaluation tracker that the evaluation of the specified + * cell is complete.

    + * + * Every successful call to startEvaluate must be followed by a + * call to endEvaluate (recommended in a finally block) to enable + * proper tracking of which cells are being evaluated at any point in time.

    + * + * Assuming a well behaved client, parameters to this method would not be + * required. However, they have been included to assert correct behaviour, + * and form more meaningful error messages. + */ + public void endEvaluate(HSSFWorkbook workbook, HSSFSheet sheet, int srcRowNum, int srcColNum) { + int nFrames = _evaluationFrames.size(); + if (nFrames < 1) { + throw new IllegalStateException("Call to endEvaluate without matching call to startEvaluate"); + } + + nFrames--; + CellEvaluationFrame cefExpected = (CellEvaluationFrame) _evaluationFrames.get(nFrames); + CellEvaluationFrame cefActual = new CellEvaluationFrame(workbook, sheet, srcRowNum, srcColNum); + if (!cefActual.equals(cefExpected)) { + throw new RuntimeException("Wrong cell specified. " + + "Corresponding startEvaluate() call was for cell {" + + cefExpected.formatAsString() + "} this endEvaluate() call is for cell {" + + cefActual.formatAsString() + "}"); + } + // else - no problems so pop current frame + _evaluationFrames.remove(nFrames); + } +} diff --git a/src/scratchpad/src/org/apache/poi/hssf/usermodel/EvaluationCycleDetectorManager.java b/src/scratchpad/src/org/apache/poi/hssf/usermodel/EvaluationCycleDetectorManager.java new file mode 100755 index 000000000..a06cd201e --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hssf/usermodel/EvaluationCycleDetectorManager.java @@ -0,0 +1,46 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hssf.usermodel; + +/** + * This class makes an EvaluationCycleDetector instance available to + * each thread via a ThreadLocal in order to avoid adding a parameter + * to a few protected methods within HSSFFormulaEvaluator. + * + * @author Josh Micich + */ +final class EvaluationCycleDetectorManager { + + ThreadLocal tl = null; + private static ThreadLocal _tlEvaluationTracker = new ThreadLocal() { + protected synchronized Object initialValue() { + return new EvaluationCycleDetector(); + } + }; + + /** + * @return + */ + public static EvaluationCycleDetector getTracker() { + return (EvaluationCycleDetector) _tlEvaluationTracker.get(); + } + + private EvaluationCycleDetectorManager() { + // no instances of this class + } +} diff --git a/src/scratchpad/src/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java b/src/scratchpad/src/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java index f60a6adaa..3fce30655 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java +++ b/src/scratchpad/src/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java @@ -14,10 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on May 5, 2005 - * - */ + package org.apache.poi.hssf.usermodel; import java.lang.reflect.Constructor; @@ -74,11 +71,13 @@ import org.apache.poi.hssf.record.formula.eval.EqualEval; import org.apache.poi.hssf.record.formula.eval.ErrorEval; import org.apache.poi.hssf.record.formula.eval.Eval; import org.apache.poi.hssf.record.formula.eval.FuncVarEval; +import org.apache.poi.hssf.record.formula.eval.FunctionEval; import org.apache.poi.hssf.record.formula.eval.GreaterEqualEval; import org.apache.poi.hssf.record.formula.eval.GreaterThanEval; import org.apache.poi.hssf.record.formula.eval.LessEqualEval; import org.apache.poi.hssf.record.formula.eval.LessThanEval; import org.apache.poi.hssf.record.formula.eval.MultiplyEval; +import org.apache.poi.hssf.record.formula.eval.NameEval; import org.apache.poi.hssf.record.formula.eval.NotEqualEval; import org.apache.poi.hssf.record.formula.eval.NumberEval; import org.apache.poi.hssf.record.formula.eval.OperationEval; @@ -91,13 +90,10 @@ import org.apache.poi.hssf.record.formula.eval.SubtractEval; import org.apache.poi.hssf.record.formula.eval.UnaryMinusEval; import org.apache.poi.hssf.record.formula.eval.UnaryPlusEval; import org.apache.poi.hssf.record.formula.eval.ValueEval; -import org.apache.poi.hssf.usermodel.HSSFSheet; /** * @author Amol S. Deshmukh < amolweb at ya hoo dot com > * - * Limitations: Unfortunately, cyclic references will cause stackoverflow - * exception */ public class HSSFFormulaEvaluator { @@ -173,7 +169,7 @@ public class HSSFFormulaEvaluator { * formula evaluated. */ public static FormulaParser getUnderlyingParser(HSSFWorkbook workbook, String formula) { - return new FormulaParser(formula, workbook.getWorkbook()); + return new FormulaParser(formula, workbook.getWorkbook()); } /** @@ -286,19 +282,19 @@ public class HSSFFormulaEvaluator { CellValue cv = getCellValueForEval(internalEvaluate(cell, row, sheet, workbook)); switch (cv.getCellType()) { case HSSFCell.CELL_TYPE_BOOLEAN: - cell.setCellType(HSSFCell.CELL_TYPE_BOOLEAN); + cell.setCellType(HSSFCell.CELL_TYPE_BOOLEAN); cell.setCellValue(cv.getBooleanValue()); break; case HSSFCell.CELL_TYPE_ERROR: - cell.setCellType(HSSFCell.CELL_TYPE_ERROR); + cell.setCellType(HSSFCell.CELL_TYPE_ERROR); cell.setCellValue(cv.getErrorValue()); break; case HSSFCell.CELL_TYPE_NUMERIC: - cell.setCellType(HSSFCell.CELL_TYPE_NUMERIC); + cell.setCellType(HSSFCell.CELL_TYPE_NUMERIC); cell.setCellValue(cv.getNumberValue()); break; case HSSFCell.CELL_TYPE_STRING: - cell.setCellType(HSSFCell.CELL_TYPE_STRING); + cell.setCellType(HSSFCell.CELL_TYPE_STRING); cell.setCellValue(cv.getRichTextStringValue()); break; case HSSFCell.CELL_TYPE_BLANK: @@ -337,6 +333,11 @@ public class HSSFFormulaEvaluator { else if (eval instanceof BlankEval) { retval = new CellValue(HSSFCell.CELL_TYPE_BLANK); } + else if (eval instanceof ErrorEval) { + retval = new CellValue(HSSFCell.CELL_TYPE_ERROR); + retval.setErrorValue((byte)((ErrorEval)eval).getErrorCode()); +// retval.setRichTextStringValue(new HSSFRichTextString("#An error occurred. check cell.getErrorCode()")); + } else { retval = new CellValue(HSSFCell.CELL_TYPE_ERROR); } @@ -348,16 +349,26 @@ public class HSSFFormulaEvaluator { * Dev. Note: Internal evaluate must be passed only a formula cell * else a runtime exception will be thrown somewhere inside the method. * (Hence this is a private method.) - * - * @param srcCell - * @param srcRow - * @param sheet - * @param workbook */ - protected static ValueEval internalEvaluate(HSSFCell srcCell, HSSFRow srcRow, HSSFSheet sheet, HSSFWorkbook workbook) { + private static ValueEval internalEvaluate(HSSFCell srcCell, HSSFRow srcRow, HSSFSheet sheet, HSSFWorkbook workbook) { int srcRowNum = srcRow.getRowNum(); short srcColNum = srcCell.getCellNum(); - FormulaParser parser = new FormulaParser(srcCell.getCellFormula(), workbook.getWorkbook()); + + + EvaluationCycleDetector tracker = EvaluationCycleDetectorManager.getTracker(); + + if(!tracker.startEvaluate(workbook, sheet, srcRowNum, srcColNum)) { + return ErrorEval.CIRCULAR_REF_ERROR; + } + try { + return evaluateCell(workbook, sheet, srcRowNum, srcColNum, srcCell.getCellFormula()); + } finally { + tracker.endEvaluate(workbook, sheet, srcRowNum, srcColNum); + } + } + private static ValueEval evaluateCell(HSSFWorkbook workbook, HSSFSheet sheet, + int srcRowNum, short srcColNum, String cellFormulaText) { + FormulaParser parser = new FormulaParser(cellFormulaText, workbook.getWorkbook()); parser.parse(); Ptg[] ptgs = parser.getRPNPtg(); // -- parsing over -- @@ -366,16 +377,25 @@ public class HSSFFormulaEvaluator { Stack stack = new Stack(); for (int i = 0, iSize = ptgs.length; i < iSize; i++) { - // since we dont know how to handle these yet :( - if (ptgs[i] instanceof ControlPtg) { continue; } - if (ptgs[i] instanceof MemErrPtg) { continue; } - if (ptgs[i] instanceof MissingArgPtg) { continue; } - if (ptgs[i] instanceof NamePtg) { continue; } - if (ptgs[i] instanceof NameXPtg) { continue; } - if (ptgs[i] instanceof UnknownPtg) { continue; } + // since we don't know how to handle these yet :( + Ptg ptg = ptgs[i]; + if (ptg instanceof ControlPtg) { continue; } + if (ptg instanceof MemErrPtg) { continue; } + if (ptg instanceof MissingArgPtg) { continue; } + if (ptg instanceof NamePtg) { + // named ranges, macro functions + NamePtg namePtg = (NamePtg) ptg; + stack.push(new NameEval(namePtg.getIndex())); + continue; + } + if (ptg instanceof NameXPtg) { + // TODO - external functions + continue; + } + if (ptg instanceof UnknownPtg) { continue; } - if (ptgs[i] instanceof OperationPtg) { - OperationPtg optg = (OperationPtg) ptgs[i]; + if (ptg instanceof OperationPtg) { + OperationPtg optg = (OperationPtg) ptg; // parens can be ignored since we have RPN tokens if (optg instanceof ParenthesisPtg) { continue; } @@ -392,85 +412,151 @@ public class HSSFFormulaEvaluator { Eval p = (Eval) stack.pop(); ops[j] = p; } - Eval opresult = operation.evaluate(ops, srcRowNum, srcColNum); + Eval opresult = invokeOperation(operation, ops, srcRowNum, srcColNum, workbook, sheet); stack.push(opresult); } - else if (ptgs[i] instanceof ReferencePtg) { - ReferencePtg ptg = (ReferencePtg) ptgs[i]; - short colnum = ptg.getColumn(); - short rownum = ptg.getRow(); - HSSFRow row = sheet.getRow(rownum); - HSSFCell cell = (row != null) ? row.getCell(colnum) : null; - pushRef2DEval(ptg, stack, cell, row, sheet, workbook); + else if (ptg instanceof ReferencePtg) { + ReferencePtg refPtg = (ReferencePtg) ptg; + int colIx = refPtg.getColumn(); + int rowIx = refPtg.getRow(); + HSSFRow row = sheet.getRow(rowIx); + HSSFCell cell = (row != null) ? row.getCell(colIx) : null; + stack.push(createRef2DEval(refPtg, cell, row, sheet, workbook)); } - else if (ptgs[i] instanceof Ref3DPtg) { - Ref3DPtg ptg = (Ref3DPtg) ptgs[i]; - short colnum = ptg.getColumn(); - short rownum = ptg.getRow(); + else if (ptg instanceof Ref3DPtg) { + Ref3DPtg refPtg = (Ref3DPtg) ptg; + int colIx = refPtg.getColumn(); + int rowIx = refPtg.getRow(); Workbook wb = workbook.getWorkbook(); - HSSFSheet xsheet = workbook.getSheetAt(wb.getSheetIndexFromExternSheetIndex(ptg.getExternSheetIndex())); - HSSFRow row = xsheet.getRow(rownum); - HSSFCell cell = (row != null) ? row.getCell(colnum) : null; - pushRef3DEval(ptg, stack, cell, row, xsheet, workbook); + HSSFSheet xsheet = workbook.getSheetAt(wb.getSheetIndexFromExternSheetIndex(refPtg.getExternSheetIndex())); + HSSFRow row = xsheet.getRow(rowIx); + HSSFCell cell = (row != null) ? row.getCell(colIx) : null; + stack.push(createRef3DEval(refPtg, cell, row, xsheet, workbook)); } - else if (ptgs[i] instanceof AreaPtg) { - AreaPtg ap = (AreaPtg) ptgs[i]; - short row0 = ap.getFirstRow(); - short col0 = ap.getFirstColumn(); - short row1 = ap.getLastRow(); - short col1 = ap.getLastColumn(); - ValueEval[] values = new ValueEval[(row1 - row0 + 1) * (col1 - col0 + 1)]; - for (short x = row0; sheet != null && x < row1 + 1; x++) { - HSSFRow row = sheet.getRow(x); - for (short y = col0; row != null && y < col1 + 1; y++) { - values[(x - row0) * (col1 - col0 + 1) + (y - col0)] = - getEvalForCell(row.getCell(y), row, sheet, workbook); - } - } - AreaEval ae = new Area2DEval(ap, values); + else if (ptg instanceof AreaPtg) { + AreaPtg ap = (AreaPtg) ptg; + AreaEval ae = evaluateAreaPtg(sheet, workbook, ap); stack.push(ae); } - else if (ptgs[i] instanceof Area3DPtg) { - Area3DPtg a3dp = (Area3DPtg) ptgs[i]; - short row0 = a3dp.getFirstRow(); - short col0 = a3dp.getFirstColumn(); - short row1 = a3dp.getLastRow(); - short col1 = a3dp.getLastColumn(); - Workbook wb = workbook.getWorkbook(); - HSSFSheet xsheet = workbook.getSheetAt(wb.getSheetIndexFromExternSheetIndex(a3dp.getExternSheetIndex())); - ValueEval[] values = new ValueEval[(row1 - row0 + 1) * (col1 - col0 + 1)]; - for (short x = row0; xsheet != null && x < row1 + 1; x++) { - HSSFRow row = xsheet.getRow(x); - for (short y = col0; row != null && y < col1 + 1; y++) { - values[(x - row0) * (col1 - col0 + 1) + (y - col0)] = - getEvalForCell(row.getCell(y), row, xsheet, workbook); - } - } - AreaEval ae = new Area3DEval(a3dp, values); + else if (ptg instanceof Area3DPtg) { + Area3DPtg a3dp = (Area3DPtg) ptg; + AreaEval ae = evaluateArea3dPtg(workbook, a3dp); stack.push(ae); } else { - Eval ptgEval = getEvalForPtg(ptgs[i]); + Eval ptgEval = getEvalForPtg(ptg); stack.push(ptgEval); } } + ValueEval value = ((ValueEval) stack.pop()); - if (value instanceof RefEval) { - RefEval rv = (RefEval) value; - value = rv.getInnerValueEval(); + if (!stack.isEmpty()) { + throw new IllegalStateException("evaluation stack not empty"); } - else if (value instanceof AreaEval) { - AreaEval ae = (AreaEval) value; - if (ae.isRow()) - value = ae.getValueAt(ae.getFirstRow(), srcColNum); - else if (ae.isColumn()) - value = ae.getValueAt(srcRowNum, ae.getFirstColumn()); - else - value = ErrorEval.VALUE_INVALID; + value = dereferenceValue(value, srcRowNum, srcColNum); + if (value instanceof BlankEval) { + // Note Excel behaviour here. A blank final final value is converted to zero. + return NumberEval.ZERO; + // Formulas _never_ evaluate to blank. If a formula appears to have evaluated to + // blank, the actual value is empty string. This can be verified with ISBLANK(). } return value; } + /** + * Dereferences a single value from any AreaEval or RefEval evaluation result. + * If the supplied evaluationResult is just a plain value, it is returned as-is. + * @return a NumberEval, StringEval, BoolEval, + * BlankEval or ErrorEval. Never null. + */ + private static ValueEval dereferenceValue(ValueEval evaluationResult, int srcRowNum, short srcColNum) { + if (evaluationResult instanceof RefEval) { + RefEval rv = (RefEval) evaluationResult; + return rv.getInnerValueEval(); + } + if (evaluationResult instanceof AreaEval) { + AreaEval ae = (AreaEval) evaluationResult; + if (ae.isRow()) { + if(ae.isColumn()) { + return ae.getValues()[0]; + } + return ae.getValueAt(ae.getFirstRow(), srcColNum); + } + if (ae.isColumn()) { + return ae.getValueAt(srcRowNum, ae.getFirstColumn()); + } + return ErrorEval.VALUE_INVALID; + } + return evaluationResult; + } + + private static Eval invokeOperation(OperationEval operation, Eval[] ops, int srcRowNum, short srcColNum, + HSSFWorkbook workbook, HSSFSheet sheet) { + + if(operation instanceof FunctionEval) { + FunctionEval fe = (FunctionEval) operation; + if(fe.isFreeRefFunction()) { + return fe.getFreeRefFunction().evaluate(ops, srcRowNum, srcColNum, workbook, sheet); + } + } + return operation.evaluate(ops, srcRowNum, srcColNum); + } + + public static AreaEval evaluateAreaPtg(HSSFSheet sheet, HSSFWorkbook workbook, AreaPtg ap) { + int row0 = ap.getFirstRow(); + int col0 = ap.getFirstColumn(); + int row1 = ap.getLastRow(); + int col1 = ap.getLastColumn(); + + // If the last row is -1, then the + // reference is for the rest of the column + // (eg C:C) + // TODO: Handle whole column ranges properly + if(row1 == -1 && row0 >= 0) { + row1 = (short)sheet.getLastRowNum(); + } + ValueEval[] values = evalArea(workbook, sheet, row0, col0, row1, col1); + return new Area2DEval(ap, values); + } + + public static AreaEval evaluateArea3dPtg(HSSFWorkbook workbook, Area3DPtg a3dp) { + int row0 = a3dp.getFirstRow(); + int col0 = a3dp.getFirstColumn(); + int row1 = a3dp.getLastRow(); + int col1 = a3dp.getLastColumn(); + Workbook wb = workbook.getWorkbook(); + HSSFSheet xsheet = workbook.getSheetAt(wb.getSheetIndexFromExternSheetIndex(a3dp.getExternSheetIndex())); + + // If the last row is -1, then the + // reference is for the rest of the column + // (eg C:C) + // TODO: Handle whole column ranges properly + if(row1 == -1 && row0 >= 0) { + row1 = (short)xsheet.getLastRowNum(); + } + + ValueEval[] values = evalArea(workbook, xsheet, row0, col0, row1, col1); + return new Area3DEval(a3dp, values); + } + + private static ValueEval[] evalArea(HSSFWorkbook workbook, HSSFSheet sheet, + int row0, int col0, int row1, int col1) { + ValueEval[] values = new ValueEval[(row1 - row0 + 1) * (col1 - col0 + 1)]; + for (int x = row0; sheet != null && x < row1 + 1; x++) { + HSSFRow row = sheet.getRow(x); + for (int y = col0; y < col1 + 1; y++) { + ValueEval cellEval; + if(row == null) { + cellEval = BlankEval.INSTANCE; + } else { + cellEval = getEvalForCell(row.getCell(y), row, sheet, workbook); + } + values[(x - row0) * (col1 - col0 + 1) + (y - col0)] = cellEval; + } + } + return values; + } + /** * returns the OperationEval concrete impl instance corresponding * to the suplied operationPtg @@ -544,104 +630,77 @@ public class HSSFFormulaEvaluator { * @param workbook */ protected static ValueEval getEvalForCell(HSSFCell cell, HSSFRow row, HSSFSheet sheet, HSSFWorkbook workbook) { - ValueEval retval = BlankEval.INSTANCE; - if (cell != null) { - switch (cell.getCellType()) { - case HSSFCell.CELL_TYPE_NUMERIC: - retval = new NumberEval(cell.getNumericCellValue()); - break; - case HSSFCell.CELL_TYPE_STRING: - retval = new StringEval(cell.getRichStringCellValue().getString()); - break; - case HSSFCell.CELL_TYPE_FORMULA: - retval = internalEvaluate(cell, row, sheet, workbook); - break; - case HSSFCell.CELL_TYPE_BOOLEAN: - retval = cell.getBooleanCellValue() ? BoolEval.TRUE : BoolEval.FALSE; - break; - case HSSFCell.CELL_TYPE_BLANK: - retval = BlankEval.INSTANCE; - break; - case HSSFCell.CELL_TYPE_ERROR: - retval = ErrorEval.UNKNOWN_ERROR; // TODO: think about this... - break; - } + + if (cell == null) { + return BlankEval.INSTANCE; } - return retval; + switch (cell.getCellType()) { + case HSSFCell.CELL_TYPE_NUMERIC: + return new NumberEval(cell.getNumericCellValue()); + case HSSFCell.CELL_TYPE_STRING: + return new StringEval(cell.getRichStringCellValue().getString()); + case HSSFCell.CELL_TYPE_FORMULA: + return internalEvaluate(cell, row, sheet, workbook); + case HSSFCell.CELL_TYPE_BOOLEAN: + return BoolEval.valueOf(cell.getBooleanCellValue()); + case HSSFCell.CELL_TYPE_BLANK: + return BlankEval.INSTANCE; + case HSSFCell.CELL_TYPE_ERROR: + return ErrorEval.valueOf(cell.getErrorCellValue()); + } + throw new RuntimeException("Unexpected cell type (" + cell.getCellType() + ")"); } /** - * create a Ref2DEval for ReferencePtg and push it on the stack. + * Creates a Ref2DEval for ReferencePtg. * Non existent cells are treated as RefEvals containing BlankEval. - * @param ptg - * @param stack - * @param cell - * @param sheet - * @param workbook */ - protected static void pushRef2DEval(ReferencePtg ptg, Stack stack, - HSSFCell cell, HSSFRow row, HSSFSheet sheet, HSSFWorkbook workbook) { - if (cell != null) - switch (cell.getCellType()) { - case HSSFCell.CELL_TYPE_NUMERIC: - stack.push(new Ref2DEval(ptg, new NumberEval(cell.getNumericCellValue()), false)); - break; - case HSSFCell.CELL_TYPE_STRING: - stack.push(new Ref2DEval(ptg, new StringEval(cell.getRichStringCellValue().getString()), false)); - break; - case HSSFCell.CELL_TYPE_FORMULA: - stack.push(new Ref2DEval(ptg, internalEvaluate(cell, row, sheet, workbook), true)); - break; - case HSSFCell.CELL_TYPE_BOOLEAN: - stack.push(new Ref2DEval(ptg, cell.getBooleanCellValue() ? BoolEval.TRUE : BoolEval.FALSE, false)); - break; - case HSSFCell.CELL_TYPE_BLANK: - stack.push(new Ref2DEval(ptg, BlankEval.INSTANCE, false)); - break; - case HSSFCell.CELL_TYPE_ERROR: - stack.push(new Ref2DEval(ptg, ErrorEval.UNKNOWN_ERROR, false)); // TODO: think abt this - break; - } - else { - stack.push(new Ref2DEval(ptg, BlankEval.INSTANCE, false)); + private static Ref2DEval createRef2DEval(ReferencePtg ptg, HSSFCell cell, + HSSFRow row, HSSFSheet sheet, HSSFWorkbook workbook) { + if (cell == null) { + return new Ref2DEval(ptg, BlankEval.INSTANCE); } + + switch (cell.getCellType()) { + case HSSFCell.CELL_TYPE_NUMERIC: + return new Ref2DEval(ptg, new NumberEval(cell.getNumericCellValue())); + case HSSFCell.CELL_TYPE_STRING: + return new Ref2DEval(ptg, new StringEval(cell.getRichStringCellValue().getString())); + case HSSFCell.CELL_TYPE_FORMULA: + return new Ref2DEval(ptg, internalEvaluate(cell, row, sheet, workbook)); + case HSSFCell.CELL_TYPE_BOOLEAN: + return new Ref2DEval(ptg, BoolEval.valueOf(cell.getBooleanCellValue())); + case HSSFCell.CELL_TYPE_BLANK: + return new Ref2DEval(ptg, BlankEval.INSTANCE); + case HSSFCell.CELL_TYPE_ERROR: + return new Ref2DEval(ptg, ErrorEval.valueOf(cell.getErrorCellValue())); + } + throw new RuntimeException("Unexpected cell type (" + cell.getCellType() + ")"); } /** - * create a Ref3DEval for Ref3DPtg and push it on the stack. - * - * @param ptg - * @param stack - * @param cell - * @param sheet - * @param workbook + * create a Ref3DEval for Ref3DPtg. */ - protected static void pushRef3DEval(Ref3DPtg ptg, Stack stack, HSSFCell cell, + private static Ref3DEval createRef3DEval(Ref3DPtg ptg, HSSFCell cell, HSSFRow row, HSSFSheet sheet, HSSFWorkbook workbook) { - if (cell != null) - switch (cell.getCellType()) { - case HSSFCell.CELL_TYPE_NUMERIC: - stack.push(new Ref3DEval(ptg, new NumberEval(cell.getNumericCellValue()), false)); - break; - case HSSFCell.CELL_TYPE_STRING: - stack.push(new Ref3DEval(ptg, new StringEval(cell.getRichStringCellValue().getString()), false)); - break; - case HSSFCell.CELL_TYPE_FORMULA: - stack.push(new Ref3DEval(ptg, internalEvaluate(cell, row, sheet, workbook), true)); - break; - case HSSFCell.CELL_TYPE_BOOLEAN: - stack.push(new Ref3DEval(ptg, cell.getBooleanCellValue() ? BoolEval.TRUE : BoolEval.FALSE, false)); - break; - case HSSFCell.CELL_TYPE_BLANK: - stack.push(new Ref3DEval(ptg, BlankEval.INSTANCE, false)); - break; - case HSSFCell.CELL_TYPE_ERROR: - stack.push(new Ref3DEval(ptg, ErrorEval.UNKNOWN_ERROR, false)); // TODO: think abt this - break; - } - else { - stack.push(new Ref3DEval(ptg, BlankEval.INSTANCE, false)); + if (cell == null) { + return new Ref3DEval(ptg, BlankEval.INSTANCE); } + switch (cell.getCellType()) { + case HSSFCell.CELL_TYPE_NUMERIC: + return new Ref3DEval(ptg, new NumberEval(cell.getNumericCellValue())); + case HSSFCell.CELL_TYPE_STRING: + return new Ref3DEval(ptg, new StringEval(cell.getRichStringCellValue().getString())); + case HSSFCell.CELL_TYPE_FORMULA: + return new Ref3DEval(ptg, internalEvaluate(cell, row, sheet, workbook)); + case HSSFCell.CELL_TYPE_BOOLEAN: + return new Ref3DEval(ptg, BoolEval.valueOf(cell.getBooleanCellValue())); + case HSSFCell.CELL_TYPE_BLANK: + return new Ref3DEval(ptg, BlankEval.INSTANCE); + case HSSFCell.CELL_TYPE_ERROR: + return new Ref3DEval(ptg, ErrorEval.valueOf(cell.getErrorCellValue())); + } + throw new RuntimeException("Unexpected cell type (" + cell.getCellType() + ")"); } /** @@ -726,15 +785,15 @@ public class HSSFFormulaEvaluator { /** * @return Returns the richTextStringValue. */ - public HSSFRichTextString getRichTextStringValue() { - return richTextStringValue; - } + public HSSFRichTextString getRichTextStringValue() { + return richTextStringValue; + } /** * @param richTextStringValue The richTextStringValue to set. */ - public void setRichTextStringValue(HSSFRichTextString richTextStringValue) { - this.richTextStringValue = richTextStringValue; - } + public void setRichTextStringValue(HSSFRichTextString richTextStringValue) { + this.richTextStringValue = richTextStringValue; + } } /** diff --git a/src/scratchpad/testcases/org/apache/poi/hdgf/data/ShortChunk1.vsd b/src/scratchpad/testcases/org/apache/poi/hdgf/data/ShortChunk1.vsd new file mode 100755 index 0000000000000000000000000000000000000000..2c1632ebae3348a5115dae7fae8b2e430e5f425e GIT binary patch literal 87040 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%0AI1x|Hg0|!}aJA|7b_vny< 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 E3#Ui8CIA2c literal 0 HcmV?d00001 diff --git a/src/scratchpad/testcases/org/apache/poi/hdgf/data/ShortChunk2.vsd b/src/scratchpad/testcases/org/apache/poi/hdgf/data/ShortChunk2.vsd new file mode 100755 index 0000000000000000000000000000000000000000..7d9a3cefc6c6d0e33e35f320bd41d4ea16a1b549 GIT binary patch literal 74752 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 Vpy90awjphzSTpwjpZ|dp_%GJ{6WRa( literal 0 HcmV?d00001 diff --git a/src/scratchpad/testcases/org/apache/poi/hdgf/data/ShortChunk3.vsd b/src/scratchpad/testcases/org/apache/poi/hdgf/data/ShortChunk3.vsd new file mode 100755 index 0000000000000000000000000000000000000000..c8bd7a190d5a19c282c30eaa0c510a213b345558 GIT binary patch literal 87040 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 literal 0 HcmV?d00001 diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/data/42464-ExpPtg-bad.xls b/src/scratchpad/testcases/org/apache/poi/hssf/data/42464-ExpPtg-bad.xls deleted file mode 100644 index 54a7edb404f7d5a106a264819f5873d6fdc2cfcb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 141824 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 diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/data/42464-ExpPtg-ok.xls b/src/scratchpad/testcases/org/apache/poi/hssf/data/42464-ExpPtg-ok.xls deleted file mode 100644 index 5ae84bc90e26fa9914ebc887b39ea7b6fd38677a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 143872 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̼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 SENDq0IR3$j^8XJUBdKiw diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/data/FormulaEvalTestData.xls b/src/scratchpad/testcases/org/apache/poi/hssf/data/FormulaEvalTestData.xls deleted file mode 100644 index cf4b6fa5011723ad05fd24152a3a3e4a451d2c98..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 130048 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

    ~ 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 diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/eventusermodel/TestMissingRecordAwareHSSFListener.java b/src/scratchpad/testcases/org/apache/poi/hssf/eventusermodel/TestMissingRecordAwareHSSFListener.java index 9c2cb2b87..a038a964e 100644 --- a/src/scratchpad/testcases/org/apache/poi/hssf/eventusermodel/TestMissingRecordAwareHSSFListener.java +++ b/src/scratchpad/testcases/org/apache/poi/hssf/eventusermodel/TestMissingRecordAwareHSSFListener.java @@ -16,14 +16,14 @@ ==================================================================== */ package org.apache.poi.hssf.eventusermodel; -import org.apache.poi.hssf.eventusermodel.HSSFEventFactory; -import org.apache.poi.hssf.eventusermodel.HSSFListener; - import java.io.File; import java.io.FileInputStream; +import java.io.IOException; import java.util.ArrayList; +import java.util.List; + +import junit.framework.TestCase; -import org.apache.poi.hssf.eventusermodel.HSSFRequest; import org.apache.poi.hssf.eventusermodel.dummyrecord.LastCellOfRowDummyRecord; import org.apache.poi.hssf.eventusermodel.dummyrecord.MissingCellDummyRecord; import org.apache.poi.hssf.eventusermodel.dummyrecord.MissingRowDummyRecord; @@ -31,31 +31,33 @@ import org.apache.poi.hssf.record.LabelSSTRecord; import org.apache.poi.hssf.record.Record; import org.apache.poi.hssf.record.RowRecord; import org.apache.poi.poifs.filesystem.POIFSFileSystem; - -import junit.framework.TestCase; - -public class TestMissingRecordAwareHSSFListener extends TestCase { - private String dirname; +/** + * Tests for MissingRecordAwareHSSFListener + */ +public final class TestMissingRecordAwareHSSFListener extends TestCase { - public TestMissingRecordAwareHSSFListener() { - dirname = System.getProperty("HSSF.testdata.path"); - } - - public void testMissingRowRecords() throws Exception { + private Record[] r; + + public void setUp() { + String dirname = System.getProperty("HSSF.testdata.path"); File f = new File(dirname + "/MissingBits.xls"); - + HSSFRequest req = new HSSFRequest(); MockHSSFListener mockListen = new MockHSSFListener(); MissingRecordAwareHSSFListener listener = new MissingRecordAwareHSSFListener(mockListen); req.addListenerForAllRecords(listener); - POIFSFileSystem fs = new POIFSFileSystem(new FileInputStream(f)); HSSFEventFactory factory = new HSSFEventFactory(); - factory.processWorkbookEvents(req, fs); - - // Check we got the dummy records - Record[] r = (Record[]) - mockListen.records.toArray(new Record[mockListen.records.size()]); + try { + POIFSFileSystem fs = new POIFSFileSystem(new FileInputStream(f)); + factory.processWorkbookEvents(req, fs); + } catch (IOException e) { + throw new RuntimeException(e); + } + r = mockListen.getRecords(); + } + + public void testMissingRowRecords() throws Exception { // We have rows 0, 1, 2, 20 and 21 int row0 = -1; @@ -105,20 +107,6 @@ public class TestMissingRecordAwareHSSFListener extends TestCase { } public void testEndOfRowRecords() throws Exception { - File f = new File(dirname + "/MissingBits.xls"); - - HSSFRequest req = new HSSFRequest(); - MockHSSFListener mockListen = new MockHSSFListener(); - MissingRecordAwareHSSFListener listener = new MissingRecordAwareHSSFListener(mockListen); - req.addListenerForAllRecords(listener); - - POIFSFileSystem fs = new POIFSFileSystem(new FileInputStream(f)); - HSSFEventFactory factory = new HSSFEventFactory(); - factory.processWorkbookEvents(req, fs); - - // Check we got the dummy records - Record[] r = (Record[]) - mockListen.records.toArray(new Record[mockListen.records.size()]); // Find the cell at 0,0 int cell00 = -1; @@ -240,20 +228,6 @@ public class TestMissingRecordAwareHSSFListener extends TestCase { public void testMissingCellRecords() throws Exception { - File f = new File(dirname + "/MissingBits.xls"); - - HSSFRequest req = new HSSFRequest(); - MockHSSFListener mockListen = new MockHSSFListener(); - MissingRecordAwareHSSFListener listener = new MissingRecordAwareHSSFListener(mockListen); - req.addListenerForAllRecords(listener); - - POIFSFileSystem fs = new POIFSFileSystem(new FileInputStream(f)); - HSSFEventFactory factory = new HSSFEventFactory(); - factory.processWorkbookEvents(req, fs); - - // Check we got the dummy records - Record[] r = (Record[]) - mockListen.records.toArray(new Record[mockListen.records.size()]); // Find the cell at 0,0 int cell00 = -1; @@ -352,25 +326,35 @@ public class TestMissingRecordAwareHSSFListener extends TestCase { assertEquals(10, mc.getColumn()); } - private static class MockHSSFListener implements HSSFListener { - private MockHSSFListener() {} - private ArrayList records = new ArrayList(); + private static final class MockHSSFListener implements HSSFListener { + public MockHSSFListener() {} + private final List _records = new ArrayList(); public void processRecord(Record record) { - records.add(record); + _records.add(record); if(record instanceof MissingRowDummyRecord) { MissingRowDummyRecord mr = (MissingRowDummyRecord)record; - System.out.println("Got dummy row " + mr.getRowNumber()); + log("Got dummy row " + mr.getRowNumber()); } if(record instanceof MissingCellDummyRecord) { MissingCellDummyRecord mc = (MissingCellDummyRecord)record; - System.out.println("Got dummy cell " + mc.getRow() + " " + mc.getColumn()); + log("Got dummy cell " + mc.getRow() + " " + mc.getColumn()); } if(record instanceof LastCellOfRowDummyRecord) { LastCellOfRowDummyRecord lc = (LastCellOfRowDummyRecord)record; - System.out.println("Got end-of row, row was " + lc.getRow() + ", last column was " + lc.getLastColumnNumber()); + log("Got end-of row, row was " + lc.getRow() + ", last column was " + lc.getLastColumnNumber()); } } + private static void log(String msg) { + if(false) { // successful tests should be quiet + System.out.println(msg); + } + } + public Record[] getRecords() { + Record[] result = new Record[_records.size()]; + _records.toArray(result); + return result; + } } } diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/model/TestFormulaParserSP.java b/src/scratchpad/testcases/org/apache/poi/hssf/model/TestFormulaParserSP.java index aa73714a0..0141e1b2a 100644 --- a/src/scratchpad/testcases/org/apache/poi/hssf/model/TestFormulaParserSP.java +++ b/src/scratchpad/testcases/org/apache/poi/hssf/model/TestFormulaParserSP.java @@ -1,4 +1,3 @@ - /* ==================================================================== Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with @@ -15,30 +14,30 @@ See the License for the specific language governing permissions and limitations under the License. ==================================================================== */ - + package org.apache.poi.hssf.model; import junit.framework.TestCase; +import org.apache.poi.hssf.model.FormulaParser.FormulaParseException; import org.apache.poi.hssf.record.formula.FuncVarPtg; import org.apache.poi.hssf.record.formula.NamePtg; import org.apache.poi.hssf.record.formula.Ptg; +import org.apache.poi.hssf.usermodel.HSSFCell; import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; import org.apache.poi.hssf.usermodel.HSSFName; +import org.apache.poi.hssf.usermodel.HSSFRow; import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator.CellValue; /** * Test the low level formula parser functionality, * but using parts which need to use the * HSSFFormulaEvaluator, which is in scratchpad */ -public class TestFormulaParserSP extends TestCase { +public final class TestFormulaParserSP extends TestCase { - public TestFormulaParserSP(String name) { - super(name); - } - public void testWithNamedRange() throws Exception { HSSFWorkbook workbook = new HSSFWorkbook(); FormulaParser fp; @@ -80,4 +79,32 @@ public class TestFormulaParserSP extends TestCase { assertEquals(FuncVarPtg.class, ptgs[1].getClass()); } + public void testEvaluateFormulaWithRowBeyond32768_Bug44539() { + + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sheet = wb.createSheet(); + wb.setSheetName(0, "Sheet1"); + + HSSFRow row = sheet.createRow(0); + HSSFCell cell = row.createCell((short)0); + cell.setCellFormula("SUM(A32769:A32770)"); + + // put some values in the cells to make the evaluation more interesting + sheet.createRow(32768).createCell((short)0).setCellValue(31); + sheet.createRow(32769).createCell((short)0).setCellValue(11); + + HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(sheet, wb); + fe.setCurrentRow(row); + CellValue result; + try { + result = fe.evaluate(cell); + } catch (FormulaParseException e) { + if(e.getMessage().equals("Found reference to named range \"A\", but that named range wasn't defined!")) { + fail("Identifed bug 44539"); + } + throw new RuntimeException(e); + } + assertEquals(HSSFCell.CELL_TYPE_NUMERIC, result.getCellType()); + assertEquals(42.0, result.getNumberValue(), 0.0); + } } diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/AllFormulaEvalTests.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/AllFormulaEvalTests.java new file mode 100755 index 000000000..3260c371c --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/AllFormulaEvalTests.java @@ -0,0 +1,38 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hssf.record.formula.eval; + +import junit.framework.Test; +import junit.framework.TestSuite; + +/** + * Collects all tests the package org.apache.poi.hssf.record.formula.eval. + * + * @author Josh Micich + */ +public class AllFormulaEvalTests { + + public static Test suite() { + TestSuite result = new TestSuite("Tests for org.apache.poi.hssf.record.formula.eval"); + result.addTestSuite(TestCircularReferences.class); + result.addTestSuite(TestExternalFunction.class); + result.addTestSuite(TestFormulasFromSpreadsheet.class); + result.addTestSuite(TestUnaryPlusEval.class); + return result; + } +} diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/GenericFormulaTestCase.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/GenericFormulaTestCase.java deleted file mode 100644 index b53e8ebc3..000000000 --- a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/GenericFormulaTestCase.java +++ /dev/null @@ -1,147 +0,0 @@ -/* -* Licensed to the Apache Software Foundation (ASF) under one or more -* contributor license agreements. See the NOTICE file distributed with -* this work for additional information regarding copyright ownership. -* The ASF licenses this file to You under the Apache License, Version 2.0 -* (the "License"); you may not use this file except in compliance with -* the License. You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ -/* - * Created on May 11, 2005 - * - */ -package org.apache.poi.hssf.record.formula.eval; - -import java.io.FileInputStream; - -import junit.framework.AssertionFailedError; -import junit.framework.TestCase; - -import org.apache.poi.hssf.record.formula.functions.TestMathX; -import org.apache.poi.hssf.usermodel.HSSFCell; -import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; -import org.apache.poi.hssf.usermodel.HSSFRow; -import org.apache.poi.hssf.usermodel.HSSFSheet; -import org.apache.poi.hssf.usermodel.HSSFWorkbook; -import org.apache.poi.hssf.util.CellReference; - -/** - * @author Amol S. Deshmukh < amolweb at ya hoo dot com > - * - */ -public class GenericFormulaTestCase extends TestCase { - - protected final static String FILENAME = System.getProperty("HSSF.testdata.path")+ "/FormulaEvalTestData.xls"; - - protected static HSSFWorkbook workbook = null; - - protected CellReference beginCell; - protected int getBeginRow() { - return beginCell.getRow(); - } - - protected short getBeginCol() { - return beginCell.getCol(); - } - - protected final HSSFCell getExpectedValueCell(HSSFSheet sheet, HSSFRow row, HSSFCell cell) { - HSSFCell retval = null; - if (sheet != null) { - row = sheet.getRow(row.getRowNum()+1); - if (row != null) { - retval = row.getCell(cell.getCellNum()); - } - } - - return retval; - } - - protected void assertEquals(String msg, HSSFCell expected, HSSFFormulaEvaluator.CellValue actual) { - if (expected != null && actual!=null) { - if (expected!=null && expected.getCellType() == HSSFCell.CELL_TYPE_STRING) { - String value = expected.getRichStringCellValue().getString(); - if (value.startsWith("#")) { - expected.setCellType(HSSFCell.CELL_TYPE_ERROR); - } - } - if (!(expected == null || actual == null)) { - switch (expected.getCellType()) { - case HSSFCell.CELL_TYPE_BLANK: - assertEquals(msg, HSSFCell.CELL_TYPE_BLANK, actual.getCellType()); - break; - case HSSFCell.CELL_TYPE_BOOLEAN: - assertEquals(msg, HSSFCell.CELL_TYPE_BOOLEAN, actual.getCellType()); - assertEquals(msg, expected.getBooleanCellValue(), actual.getBooleanValue()); - break; - case HSSFCell.CELL_TYPE_ERROR: - assertEquals(msg, HSSFCell.CELL_TYPE_ERROR, actual.getCellType()); // TODO: check if exact error matches - break; - case HSSFCell.CELL_TYPE_FORMULA: // will never be used, since we will call method after formula evaluation - throw new AssertionFailedError("Cannot expect formula as result of formula evaluation: " + msg); - case HSSFCell.CELL_TYPE_NUMERIC: - assertEquals(msg, HSSFCell.CELL_TYPE_NUMERIC, actual.getCellType()); - TestMathX.assertEquals(msg, expected.getNumericCellValue(), actual.getNumberValue(), TestMathX.POS_ZERO, TestMathX.DIFF_TOLERANCE_FACTOR); -// double delta = Math.abs(expected.getNumericCellValue()-actual.getNumberValue()); -// double pctExpected = Math.abs(0.00001*expected.getNumericCellValue()); -// assertTrue(msg, delta <= pctExpected); - break; - case HSSFCell.CELL_TYPE_STRING: - assertEquals(msg, HSSFCell.CELL_TYPE_STRING, actual.getCellType()); - assertEquals(msg, expected.getRichStringCellValue().getString(), actual.getRichTextStringValue().getString()); - break; - } - } - else { - throw new AssertionFailedError("expected: " + expected + " got:" + actual); - } - } - } - - public GenericFormulaTestCase(String beginCell) throws Exception { - super("genericTest"); - if (workbook == null) { - FileInputStream fin = new FileInputStream( FILENAME ); - workbook = new HSSFWorkbook( fin ); - fin.close(); - } - this.beginCell = new CellReference(beginCell); - } - - public void setUp() { - } - - public void genericTest() throws Exception { - HSSFSheet s = workbook.getSheetAt( 0 ); - HSSFRow r = s.getRow(getBeginRow()); - short endcolnum = r.getLastCellNum(); - HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(s, workbook); - evaluator.setCurrentRow(r); - - HSSFCell c = null; - for (short colnum=getBeginCol(); colnum < endcolnum; colnum++) { - try { - c = r.getCell(colnum); - if (c==null || c.getCellType() != HSSFCell.CELL_TYPE_FORMULA) - continue; - - HSSFFormulaEvaluator.CellValue actualValue = evaluator.evaluate(c); - - HSSFCell expectedValueCell = getExpectedValueCell(s, r, c); - assertEquals("Formula: " + c.getCellFormula() - + " @ " + getBeginRow() + ":" + colnum, - expectedValueCell, actualValue); - } catch (RuntimeException re) { - throw new RuntimeException("CELL["+getBeginRow()+","+colnum+"]: "+re.getMessage(), re); - } - } - } - -} diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestCircularReferences.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestCircularReferences.java new file mode 100755 index 000000000..72db658f7 --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestCircularReferences.java @@ -0,0 +1,125 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hssf.record.formula.eval; + +import junit.framework.AssertionFailedError; +import junit.framework.TestCase; + +import org.apache.poi.hssf.usermodel.HSSFCell; +import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; +import org.apache.poi.hssf.usermodel.HSSFRow; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator.CellValue; +/** + * Tests HSSFFormulaEvaluator for its handling of cell formula circular references. + * + * @author Josh Micich + */ +public final class TestCircularReferences extends TestCase { + /** + * Translates StackOverflowError into AssertionFailedError + */ + private static CellValue evaluateWithCycles(HSSFWorkbook wb, HSSFSheet sheet, HSSFRow row, HSSFCell testCell) + throws AssertionFailedError { + HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(sheet, wb); + evaluator.setCurrentRow(row); + try { + return evaluator.evaluate(testCell); + } catch (StackOverflowError e) { + throw new AssertionFailedError( "circular reference caused stack overflow error"); + } + } + /** + * Makes sure that the specified evaluated cell value represents a circular reference error. + */ + private static void confirmCycleErrorCode(CellValue cellValue) { + assertTrue(cellValue.getCellType() == HSSFCell.CELL_TYPE_ERROR); + assertEquals(ErrorEval.CIRCULAR_REF_ERROR.getErrorCode(), cellValue.getErrorValue()); + } + + + /** + * ASF Bugzilla Bug 44413 + * "INDEX() formula cannot contain its own location in the data array range" + */ + public void testIndexFormula() { + + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sheet = wb.createSheet("Sheet1"); + + short colB = 1; + sheet.createRow(0).createCell(colB).setCellValue(1); + sheet.createRow(1).createCell(colB).setCellValue(2); + sheet.createRow(2).createCell(colB).setCellValue(3); + HSSFRow row4 = sheet.createRow(3); + HSSFCell testCell = row4.createCell((short)0); + // This formula should evaluate to the contents of B2, + testCell.setCellFormula("INDEX(A1:B4,2,2)"); + // However the range A1:B4 also includes the current cell A4. If the other parameters + // were 4 and 1, this would represent a circular reference. Since POI 'fully' evaluates + // arguments before invoking operators, POI must handle such potential cycles gracefully. + + + CellValue cellValue = evaluateWithCycles(wb, sheet, row4, testCell); + + assertTrue(cellValue.getCellType() == HSSFCell.CELL_TYPE_NUMERIC); + assertEquals(2, cellValue.getNumberValue(), 0); + } + + /** + * Cell A1 has formula "=A1" + */ + public void testSimpleCircularReference() { + + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sheet = wb.createSheet("Sheet1"); + + HSSFRow row = sheet.createRow(0); + HSSFCell testCell = row.createCell((short)0); + testCell.setCellFormula("A1"); + + HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(sheet, wb); + evaluator.setCurrentRow(row); + CellValue cellValue = evaluateWithCycles(wb, sheet, row, testCell); + + confirmCycleErrorCode(cellValue); + } + + /** + * A1=B1, B1=C1, C1=D1, D1=A1 + */ + public void testMultiLevelCircularReference() { + + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sheet = wb.createSheet("Sheet1"); + + HSSFRow row = sheet.createRow(0); + row.createCell((short)0).setCellFormula("B1"); + row.createCell((short)1).setCellFormula("C1"); + row.createCell((short)2).setCellFormula("D1"); + HSSFCell testCell = row.createCell((short)3); + testCell.setCellFormula("A1"); + + HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(sheet, wb); + evaluator.setCurrentRow(row); + CellValue cellValue = evaluateWithCycles(wb, sheet, row, testCell); + + confirmCycleErrorCode(cellValue); + } +} diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestEverything.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestEverything.java deleted file mode 100644 index ac3ca2eb2..000000000 --- a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestEverything.java +++ /dev/null @@ -1,59 +0,0 @@ -/* -* Licensed to the Apache Software Foundation (ASF) under one or more -* contributor license agreements. See the NOTICE file distributed with -* this work for additional information regarding copyright ownership. -* The ASF licenses this file to You under the Apache License, Version 2.0 -* (the "License"); you may not use this file except in compliance with -* the License. You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ -/* - * Created on May 11, 2005 - * - */ -package org.apache.poi.hssf.record.formula.eval; - -import junit.framework.TestSuite; - -/** - * This is a test of all the Eval functions we have implemented. - * Add newly implemented Eval functions in here to have them - * tested. - * For newly implemented functions, - * @see org.apache.poi.hssf.record.formula.functions.TestEverything - * - * @author Amol S. Deshmukh < amolweb at ya hoo dot com > - */ -public class TestEverything extends TestSuite { - - public static TestSuite suite() throws Exception { - TestSuite suite = new TestSuite("Tests for OperationEval concrete implementation classes."); - suite.addTest(new GenericFormulaTestCase("D23")); // Add - suite.addTest(new GenericFormulaTestCase("D27")); // ConcatEval - suite.addTest(new GenericFormulaTestCase("D31")); // DivideEval - suite.addTest(new GenericFormulaTestCase("D35")); // EqualEval - suite.addTest(new GenericFormulaTestCase("D39")); // GreaterEqualEval - suite.addTest(new GenericFormulaTestCase("D43")); // GreaterThanEval - suite.addTest(new GenericFormulaTestCase("D47")); // LessEqualEval - suite.addTest(new GenericFormulaTestCase("D51")); // LessThanEval - suite.addTest(new GenericFormulaTestCase("D55")); // MultiplyEval - suite.addTest(new GenericFormulaTestCase("D59")); // NotEqualEval - suite.addTest(new GenericFormulaTestCase("D63")); // PowerEval - suite.addTest(new GenericFormulaTestCase("D67")); // SubtractEval - suite.addTest(new GenericFormulaTestCase("D71")); // UnaryMinusEval - suite.addTest(new GenericFormulaTestCase("D75")); // UnaryPlusEval - - // Add newly implemented Eval functions here - // (Formula functions go in - // @see org.apache.poi.hssf.record.formula.functions.TestEverything ) - - return suite; - } -} diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestExternalFunction.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestExternalFunction.java new file mode 100755 index 000000000..27e333865 --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestExternalFunction.java @@ -0,0 +1,61 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hssf.record.formula.eval; + +import junit.framework.TestCase; + +import org.apache.poi.hssf.usermodel.HSSFCell; +import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; +import org.apache.poi.hssf.usermodel.HSSFName; +import org.apache.poi.hssf.usermodel.HSSFRow; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator.CellValue; +/** + * + * @author Josh Micich + */ +public final class TestExternalFunction extends TestCase { + + /** + * Checks that an external function can get invoked from the formula evaluator. + */ + public void testInvoke() { + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sheet = wb.createSheet(); + wb.setSheetName(0, "Sheet1"); + HSSFRow row = sheet.createRow(0); + HSSFCell cell = row.createCell((short)0); + + HSSFName hssfName = wb.createName(); + hssfName.setNameName("myFunc"); + + cell.setCellFormula("myFunc()"); + String actualFormula=cell.getCellFormula(); + assertEquals("myFunc()", actualFormula); + + HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(sheet, wb); + fe.setCurrentRow(row); + CellValue evalResult = fe.evaluate(cell); + + // Check the return value from ExternalFunction.evaluate() + // TODO - make this test assert something more interesting as soon as ExternalFunction works a bit better + assertEquals(HSSFCell.CELL_TYPE_ERROR, evalResult.getCellType()); + assertEquals(ErrorEval.FUNCTION_NOT_IMPLEMENTED.getErrorCode(), evalResult.getErrorValue()); + } +} diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestFormulasFromSpreadsheet.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestFormulasFromSpreadsheet.java new file mode 100644 index 000000000..f57221c9b --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestFormulasFromSpreadsheet.java @@ -0,0 +1,329 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. See the NOTICE file distributed with +* this work for additional information regarding copyright ownership. +* The ASF licenses this file to You under the Apache License, Version 2.0 +* (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + + +package org.apache.poi.hssf.record.formula.eval; + +import java.io.FileInputStream; +import java.io.PrintStream; + +import junit.framework.Assert; +import junit.framework.AssertionFailedError; +import junit.framework.TestCase; + +import org.apache.poi.hssf.record.formula.functions.TestMathX; +import org.apache.poi.hssf.usermodel.HSSFCell; +import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; +import org.apache.poi.hssf.usermodel.HSSFRow; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; + +/** + * Tests formulas and operators as loaded from a test data spreadsheet.

    + * This class does not test implementors of Function and OperationEval in + * isolation. Much of the evaluation engine (i.e. HSSFFormulaEvaluator, ...) gets + * exercised as well. Tests for bug fixes and specific/tricky behaviour can be found in the + * corresponding test class (TestXxxx) of the target (Xxxx) implementor, + * where execution can be observed more easily. + * + * @author Amol S. Deshmukh < amolweb at ya hoo dot com > + */ +public final class TestFormulasFromSpreadsheet extends TestCase { + + private static final class Result { + public static final int SOME_EVALUATIONS_FAILED = -1; + public static final int ALL_EVALUATIONS_SUCCEEDED = +1; + public static final int NO_EVALUATIONS_FOUND = 0; + } + + /** + * This class defines constants for navigating around the test data spreadsheet used for these tests. + */ + private static final class SS { + + /** + * Name of the test spreadsheet (found in the standard test data folder) + */ + public final static String FILENAME = "FormulaEvalTestData.xls"; + /** + * Row (zero-based) in the test spreadsheet where the operator examples start. + */ + public static final int START_OPERATORS_ROW_INDEX = 22; // Row '23' + /** + * Row (zero-based) in the test spreadsheet where the function examples start. + */ + public static final int START_FUNCTIONS_ROW_INDEX = 83; // Row '84' + /** + * Index of the column that contains the function names + */ + public static final short COLUMN_INDEX_FUNCTION_NAME = 1; // Column 'B' + + /** + * Used to indicate when there are no more functions left + */ + public static final String FUNCTION_NAMES_END_SENTINEL = ""; + + /** + * Index of the column where the test values start (for each function) + */ + public static final short COLUMN_INDEX_FIRST_TEST_VALUE = 3; // Column 'D' + + /** + * Each function takes 4 rows in the test spreadsheet + */ + public static final int NUMBER_OF_ROWS_PER_FUNCTION = 4; + } + + private HSSFWorkbook workbook; + private HSSFSheet sheet; + // Note - multiple failures are aggregated before ending. + // If one or more functions fail, a single AssertionFailedError is thrown at the end + private int _functionFailureCount; + private int _functionSuccessCount; + private int _evaluationFailureCount; + private int _evaluationSuccessCount; + + private static final HSSFCell getExpectedValueCell(HSSFRow row, short columnIndex) { + if (row == null) { + return null; + } + return row.getCell(columnIndex); + } + + + private static void confirmExpectedResult(String msg, HSSFCell expected, HSSFFormulaEvaluator.CellValue actual) { + if (expected == null) { + throw new AssertionFailedError(msg + " - Bad setup data expected value is null"); + } + if(actual == null) { + throw new AssertionFailedError(msg + " - actual value was null"); + } + + if (expected.getCellType() == HSSFCell.CELL_TYPE_STRING) { + String value = expected.getRichStringCellValue().getString(); + if (value.startsWith("#")) { + // TODO - this code never called + expected.setCellType(HSSFCell.CELL_TYPE_ERROR); + // expected.setCellErrorValue(...?); + } + } + + switch (expected.getCellType()) { + case HSSFCell.CELL_TYPE_BLANK: + assertEquals(msg, HSSFCell.CELL_TYPE_BLANK, actual.getCellType()); + break; + case HSSFCell.CELL_TYPE_BOOLEAN: + assertEquals(msg, HSSFCell.CELL_TYPE_BOOLEAN, actual.getCellType()); + assertEquals(msg, expected.getBooleanCellValue(), actual.getBooleanValue()); + break; + case HSSFCell.CELL_TYPE_ERROR: + assertEquals(msg, HSSFCell.CELL_TYPE_ERROR, actual.getCellType()); + if(false) { // TODO: fix ~45 functions which are currently returning incorrect error values + assertEquals(msg, expected.getErrorCellValue(), actual.getErrorValue()); + } + break; + case HSSFCell.CELL_TYPE_FORMULA: // will never be used, since we will call method after formula evaluation + throw new AssertionFailedError("Cannot expect formula as result of formula evaluation: " + msg); + case HSSFCell.CELL_TYPE_NUMERIC: + assertEquals(msg, HSSFCell.CELL_TYPE_NUMERIC, actual.getCellType()); + TestMathX.assertEquals(msg, expected.getNumericCellValue(), actual.getNumberValue(), TestMathX.POS_ZERO, TestMathX.DIFF_TOLERANCE_FACTOR); +// double delta = Math.abs(expected.getNumericCellValue()-actual.getNumberValue()); +// double pctExpected = Math.abs(0.00001*expected.getNumericCellValue()); +// assertTrue(msg, delta <= pctExpected); + break; + case HSSFCell.CELL_TYPE_STRING: + assertEquals(msg, HSSFCell.CELL_TYPE_STRING, actual.getCellType()); + assertEquals(msg, expected.getRichStringCellValue().getString(), actual.getRichTextStringValue().getString()); + break; + } + } + + + protected void setUp() throws Exception { + if (workbook == null) { + String filePath = System.getProperty("HSSF.testdata.path")+ "/" + SS.FILENAME; + FileInputStream fin = new FileInputStream( filePath ); + workbook = new HSSFWorkbook( fin ); + sheet = workbook.getSheetAt( 0 ); + } + _functionFailureCount = 0; + _functionSuccessCount = 0; + _evaluationFailureCount = 0; + _evaluationSuccessCount = 0; + } + + public void testFunctionsFromTestSpreadsheet() { + + processFunctionGroup(SS.START_OPERATORS_ROW_INDEX, null); + processFunctionGroup(SS.START_FUNCTIONS_ROW_INDEX, null); + // example for debugging individual functions/operators: +// processFunctionGroup(SS.START_OPERATORS_ROW_INDEX, "ConcatEval"); +// processFunctionGroup(SS.START_FUNCTIONS_ROW_INDEX, "AVERAGE"); + + // confirm results + String successMsg = "There were " + + _evaluationSuccessCount + " successful evaluation(s) and " + + _functionSuccessCount + " function(s) without error"; + if(_functionFailureCount > 0) { + String msg = _functionFailureCount + " function(s) failed in " + + _evaluationFailureCount + " evaluation(s). " + successMsg; + throw new AssertionFailedError(msg); + } + if(false) { // normally no output for successful tests + System.out.println(getClass().getName() + ": " + successMsg); + } + } + + /** + * @param startRowIndex row index in the spreadsheet where the first function/operator is found + * @param testFocusFunctionName name of a single function/operator to test alone. + * Typically pass null to test all functions + */ + private void processFunctionGroup(int startRowIndex, String testFocusFunctionName) { + + HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(sheet, workbook); + + int rowIndex = startRowIndex; + while (true) { + HSSFRow r = sheet.getRow(rowIndex); + String targetFunctionName = getTargetFunctionName(r); + if(targetFunctionName == null) { + throw new AssertionFailedError("Test spreadsheet cell empty on row (" + + (rowIndex+1) + "). Expected function name or '" + + SS.FUNCTION_NAMES_END_SENTINEL + "'"); + } + if(targetFunctionName.equals(SS.FUNCTION_NAMES_END_SENTINEL)) { + // found end of functions list + break; + } + if(testFocusFunctionName == null || targetFunctionName.equalsIgnoreCase(testFocusFunctionName)) { + + // expected results are on the row below + HSSFRow expectedValuesRow = sheet.getRow(rowIndex + 1); + if(expectedValuesRow == null) { + int missingRowNum = rowIndex + 2; //+1 for 1-based, +1 for next row + throw new AssertionFailedError("Missing expected values row for function '" + + targetFunctionName + " (row " + missingRowNum + ")"); + } + switch(processFunctionRow(evaluator, targetFunctionName, r, expectedValuesRow)) { + case Result.ALL_EVALUATIONS_SUCCEEDED: _functionSuccessCount++; break; + case Result.SOME_EVALUATIONS_FAILED: _functionFailureCount++; break; + default: + throw new RuntimeException("unexpected result"); + case Result.NO_EVALUATIONS_FOUND: // do nothing + } + } + rowIndex += SS.NUMBER_OF_ROWS_PER_FUNCTION; + } + } + + /** + * + * @return a constant from the local Result class denoting whether there were any evaluation + * cases, and whether they all succeeded. + */ + private int processFunctionRow(HSSFFormulaEvaluator evaluator, String targetFunctionName, + HSSFRow formulasRow, HSSFRow expectedValuesRow) { + + int result = Result.NO_EVALUATIONS_FOUND; // so far + short endcolnum = formulasRow.getLastCellNum(); + evaluator.setCurrentRow(formulasRow); + + // iterate across the row for all the evaluation cases + for (short colnum=SS.COLUMN_INDEX_FIRST_TEST_VALUE; colnum < endcolnum; colnum++) { + HSSFCell c = formulasRow.getCell(colnum); + if (c == null || c.getCellType() != HSSFCell.CELL_TYPE_FORMULA) { + continue; + } + + HSSFFormulaEvaluator.CellValue actualValue = evaluator.evaluate(c); + + HSSFCell expectedValueCell = getExpectedValueCell(expectedValuesRow, colnum); + try { + confirmExpectedResult("Function '" + targetFunctionName + "': Formula: " + c.getCellFormula() + " @ " + formulasRow.getRowNum() + ":" + colnum, + expectedValueCell, actualValue); + _evaluationSuccessCount ++; + if(result != Result.SOME_EVALUATIONS_FAILED) { + result = Result.ALL_EVALUATIONS_SUCCEEDED; + } + } catch (AssertionFailedError e) { + _evaluationFailureCount ++; + printShortStackTrace(System.err, e); + result = Result.SOME_EVALUATIONS_FAILED; + } + } + return result; + } + + /** + * Useful to keep output concise when expecting many failures to be reported by this test case + */ + private static void printShortStackTrace(PrintStream ps, AssertionFailedError e) { + StackTraceElement[] stes = e.getStackTrace(); + + int startIx = 0; + // skip any top frames inside junit.framework.Assert + while(startIx= endIx) { + // something went wrong. just print the whole stack trace + e.printStackTrace(ps); + } + endIx -= 4; // skip 4 frames of reflection invocation + ps.println(e.toString()); + for(int i=startIx; inull if cell is missing, empty or blank + */ + private static String getTargetFunctionName(HSSFRow r) { + if(r == null) { + System.err.println("Warning - given null row, can't figure out function name"); + return null; + } + HSSFCell cell = r.getCell(SS.COLUMN_INDEX_FUNCTION_NAME); + if(cell == null) { + System.err.println("Warning - Row " + r.getRowNum() + " has no cell " + SS.COLUMN_INDEX_FUNCTION_NAME + ", can't figure out function name"); + return null; + } + if(cell.getCellType() == HSSFCell.CELL_TYPE_BLANK) { + return null; + } + if(cell.getCellType() == HSSFCell.CELL_TYPE_STRING) { + return cell.getRichStringCellValue().getString(); + } + + throw new AssertionFailedError("Bad cell type for 'function name' column: (" + + cell.getCellType() + ") row (" + (r.getRowNum() +1) + ")"); + } +} diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestUnaryPlusEval.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestUnaryPlusEval.java new file mode 100755 index 000000000..724c54cd9 --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestUnaryPlusEval.java @@ -0,0 +1,61 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. See the NOTICE file distributed with +* this work for additional information regarding copyright ownership. +* The ASF licenses this file to You under the Apache License, Version 2.0 +* (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + + +package org.apache.poi.hssf.record.formula.eval; + +import org.apache.poi.hssf.record.formula.AreaPtg; +import org.apache.poi.hssf.record.formula.UnaryPlusPtg; +import org.apache.poi.hssf.record.formula.functions.NumericFunctionInvoker; + +import junit.framework.TestCase; + +/** + * Test for unary plus operator evaluator. + * + * @author Josh Micich + */ +public final class TestUnaryPlusEval extends TestCase { + + /** + * Test for bug observable at svn revision 618865 (5-Feb-2008)
    + * The code for handling column operands had been copy-pasted from the row handling code. + */ + public void testColumnOperand() { + + short firstRow = (short)8; + short lastRow = (short)12; + short colNum = (short)5; + AreaPtg areaPtg = new AreaPtg(firstRow, lastRow, colNum, colNum, false, false, false, false); + ValueEval[] values = { + new NumberEval(27), + new NumberEval(29), + new NumberEval(35), // value in row 10 + new NumberEval(37), + new NumberEval(38), + }; + Eval areaEval = new Area2DEval(areaPtg, values); + Eval[] args = { + areaEval, + }; + + double result = NumericFunctionInvoker.invoke(new UnaryPlusEval(new UnaryPlusPtg()), args, 10, (short)20); + + assertEquals(35, result, 0); + } + +} diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java index b5e084367..d3e9c4c41 100755 --- a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java +++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java @@ -31,13 +31,23 @@ public final class AllIndividualFunctionEvaluationTests { // TODO - have this suite incorporated into a higher level one public static Test suite() { TestSuite result = new TestSuite("Tests for org.apache.poi.hssf.record.formula.functions"); + result.addTestSuite(TestAverage.class); result.addTestSuite(TestCountFuncs.class); result.addTestSuite(TestDate.class); result.addTestSuite(TestFinanceLib.class); result.addTestSuite(TestIndex.class); + result.addTestSuite(TestIsBlank.class); + result.addTestSuite(TestLen.class); + result.addTestSuite(TestMid.class); result.addTestSuite(TestMathX.class); + result.addTestSuite(TestMatch.class); + result.addTestSuite(TestOffset.class); result.addTestSuite(TestRowCol.class); + result.addTestSuite(TestSumproduct.class); result.addTestSuite(TestStatsLib.class); + result.addTestSuite(TestTFunc.class); + result.addTestSuite(TestTrim.class); + result.addTestSuite(TestXYNumericFunction.class); return result; } diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/EvalFactory.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/EvalFactory.java index 958c48664..a6e262b86 100755 --- a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/EvalFactory.java +++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/EvalFactory.java @@ -58,6 +58,6 @@ final class EvalFactory { * Creates a single RefEval (with value zero) */ public static RefEval createRefEval(String refStr) { - return new Ref2DEval(new ReferencePtg(refStr), ZERO, true); + return new Ref2DEval(new ReferencePtg(refStr), ZERO); } } diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/NumericFunctionInvoker.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/NumericFunctionInvoker.java index 87405a491..d47723134 100755 --- a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/NumericFunctionInvoker.java +++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/NumericFunctionInvoker.java @@ -23,13 +23,14 @@ import junit.framework.AssertionFailedError; import org.apache.poi.hssf.record.formula.eval.ErrorEval; import org.apache.poi.hssf.record.formula.eval.Eval; import org.apache.poi.hssf.record.formula.eval.NumericValueEval; +import org.apache.poi.hssf.record.formula.eval.OperationEval; /** * Test helper class for invoking functions with numeric results. * * @author Josh Micich */ -final class NumericFunctionInvoker { +public final class NumericFunctionInvoker { private NumericFunctionInvoker() { // no instances of this class @@ -59,13 +60,37 @@ final class NumericFunctionInvoker { + ") failed: " + e.getMessage()); } + } + /** + * Invokes the specified operator with the arguments. + *

    + * This method cannot be used for confirming error return codes. Any non-numeric evaluation + * result causes the current junit test to fail. + */ + public static double invoke(OperationEval f, Eval[] args, int srcCellRow, int srcCellCol) { + try { + return invokeInternal(f, args, srcCellRow, srcCellCol); + } catch (NumericEvalEx e) { + throw new AssertionFailedError("Evaluation of function (" + f.getClass().getName() + + ") failed: " + e.getMessage()); + } + } /** * Formats nicer error messages for the junit output */ - private static double invokeInternal(Function f, Eval[] args, int srcCellRow, int srcCellCol) + private static double invokeInternal(Object target, Eval[] args, int srcCellRow, int srcCellCol) throws NumericEvalEx { - Eval evalResult = f.evaluate(args, srcCellRow, (short)srcCellCol); + Eval evalResult; + // TODO - make OperationEval extend Function + if (target instanceof Function) { + Function ff = (Function) target; + evalResult = ff.evaluate(args, srcCellRow, (short)srcCellCol); + } else { + OperationEval ff = (OperationEval) target; + evalResult = ff.evaluate(args, srcCellRow, (short)srcCellCol); + } + if(evalResult == null) { throw new NumericEvalEx("Result object was null"); } @@ -86,8 +111,8 @@ final class NumericFunctionInvoker { if(errorCodesAreEqual(ee, ErrorEval.FUNCTION_NOT_IMPLEMENTED)) { return "Function not implemented"; } - if(errorCodesAreEqual(ee, ErrorEval.UNKNOWN_ERROR)) { - return "Unknown error"; + if(errorCodesAreEqual(ee, ErrorEval.VALUE_INVALID)) { + return "Error code: #VALUE! (invalid value)"; } return "Error code=" + ee.getErrorCode(); } diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestAverage.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestAverage.java new file mode 100755 index 000000000..4f0e5fff2 --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestAverage.java @@ -0,0 +1,103 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hssf.record.formula.functions; + +import junit.framework.TestCase; + +import org.apache.poi.hssf.record.formula.eval.BlankEval; +import org.apache.poi.hssf.record.formula.eval.BoolEval; +import org.apache.poi.hssf.record.formula.eval.ErrorEval; +import org.apache.poi.hssf.record.formula.eval.Eval; +import org.apache.poi.hssf.record.formula.eval.NumberEval; +import org.apache.poi.hssf.record.formula.eval.ValueEval; +/** + * Tests for Excel function AVERAGE() + * + * @author Josh Micich + */ +public final class TestAverage extends TestCase { + + + private static Eval invokeAverage(Eval[] args) { + return new Average().evaluate(args, -1, (short)-1); + } + + private void confirmAverage(Eval[] args, double expected) { + Eval result = invokeAverage(args); + assertEquals(NumberEval.class, result.getClass()); + assertEquals(expected, ((NumberEval)result).getNumberValue(), 0); + } + + private void confirmAverage(Eval[] args, ErrorEval expectedError) { + Eval result = invokeAverage(args); + assertEquals(ErrorEval.class, result.getClass()); + assertEquals(expectedError.getErrorCode(), ((ErrorEval)result).getErrorCode()); + } + + public void testBasic() { + + ValueEval[] values = { + new NumberEval(1), + new NumberEval(2), + new NumberEval(3), + new NumberEval(4), + }; + + confirmAverage(values, 2.5); + + values = new ValueEval[] { + new NumberEval(1), + new NumberEval(2), + BlankEval.INSTANCE, + new NumberEval(3), + BlankEval.INSTANCE, + new NumberEval(4), + BlankEval.INSTANCE, + }; + + confirmAverage(values, 2.5); + } + + /** + * Valid cases where values are not pure numbers + */ + public void testUnusualArgs() { + ValueEval[] values = { + new NumberEval(1), + new NumberEval(2), + BoolEval.TRUE, + BoolEval.FALSE, + }; + + confirmAverage(values, 1.0); + + } + + // currently disabled because MultiOperandNumericFunction.getNumberArray(Eval[], int, short) + // does not handle error values properly yet + public void XtestErrors() { + ValueEval[] values = { + new NumberEval(1), + ErrorEval.NAME_INVALID, + new NumberEval(3), + ErrorEval.DIV_ZERO, + }; + confirmAverage(values, ErrorEval.NAME_INVALID); + + } +} diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestCountFuncs.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestCountFuncs.java index fbaace921..ae93a2d41 100755 --- a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestCountFuncs.java +++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestCountFuncs.java @@ -125,7 +125,7 @@ public final class TestCountFuncs extends TestCase { }; Area2DEval arg0 = new Area2DEval(new AreaPtg("C1:C6"), values); - Ref2DEval criteriaArg = new Ref2DEval(new ReferencePtg("A1"), new NumberEval(25), true); + Ref2DEval criteriaArg = new Ref2DEval(new ReferencePtg("A1"), new NumberEval(25)); Eval[] args= { arg0, criteriaArg, }; double actual = NumericFunctionInvoker.invoke(new Countif(), args); diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestEverything.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestEverything.java deleted file mode 100644 index 833781021..000000000 --- a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestEverything.java +++ /dev/null @@ -1,48 +0,0 @@ -/* -* Licensed to the Apache Software Foundation (ASF) under one or more -* contributor license agreements. See the NOTICE file distributed with -* this work for additional information regarding copyright ownership. -* The ASF licenses this file to You under the Apache License, Version 2.0 -* (the "License"); you may not use this file except in compliance with -* the License. You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ -/* - * Created on May 11, 2005 - * - */ -package org.apache.poi.hssf.record.formula.functions; - -import org.apache.poi.hssf.record.formula.eval.GenericFormulaTestCase; - -import junit.framework.TestSuite; - -/** - * This is a test of all the normal formula functions we have implemented. - * It should pick up newly implemented functions which are correctly added - * to the test formula excel file, but tweak the rows below if you - * add any past the end of what's currently checked. - * For newly implemented eval functions, - * @see org.apache.poi.hssf.record.formula.eval.TestEverything - * - * @author Amol S. Deshmukh < amolweb at ya hoo dot com > - */ -public class TestEverything extends TestSuite { - public static TestSuite suite() throws Exception { - TestSuite suite = new TestSuite("Tests for individual function classes"); - String s; - for(int i=80; i<1485;i=i+4) { - s = "D"+Integer.toString(i).trim(); - suite.addTest(new GenericFormulaTestCase(s)); - } -// suite.addTest(new GenericFormulaTestCase("D1164")); - return suite; - } -} diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestIsBlank.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestIsBlank.java new file mode 100755 index 000000000..7ce2bd245 --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestIsBlank.java @@ -0,0 +1,62 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hssf.record.formula.functions; + +import junit.framework.TestCase; + +import org.apache.poi.hssf.usermodel.HSSFCell; +import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; +import org.apache.poi.hssf.usermodel.HSSFRow; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator.CellValue; +/** + * Tests for Excel function ISBLANK() + * + * @author Josh Micich + */ +public final class TestIsBlank extends TestCase { + + + + public void test3DArea() { + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sheet1 = wb.createSheet(); + wb.setSheetName(0, "Sheet1"); + wb.createSheet(); + wb.setSheetName(1, "Sheet2"); + HSSFRow row = sheet1.createRow(0); + HSSFCell cell = row.createCell((short)0); + + + cell.setCellFormula("isblank(Sheet2!A1:A1)"); + + HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(sheet1, wb); + fe.setCurrentRow(row); + CellValue result = fe.evaluate(cell); + assertEquals(HSSFCell.CELL_TYPE_BOOLEAN, result.getCellType()); + assertEquals(true, result.getBooleanValue()); + + cell.setCellFormula("isblank(D7:D7)"); + + result = fe.evaluate(cell); + assertEquals(HSSFCell.CELL_TYPE_BOOLEAN, result.getCellType()); + assertEquals(true, result.getBooleanValue()); + + } +} diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestLen.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestLen.java new file mode 100755 index 000000000..a96fb4e2b --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestLen.java @@ -0,0 +1,73 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hssf.record.formula.functions; + +import junit.framework.TestCase; + +import org.apache.poi.hssf.record.formula.eval.BlankEval; +import org.apache.poi.hssf.record.formula.eval.BoolEval; +import org.apache.poi.hssf.record.formula.eval.ErrorEval; +import org.apache.poi.hssf.record.formula.eval.Eval; +import org.apache.poi.hssf.record.formula.eval.NumberEval; +import org.apache.poi.hssf.record.formula.eval.StringEval; +/** + * Tests for Excel function LEN() + * + * @author Josh Micich + */ +public final class TestLen extends TestCase { + + + private static Eval invokeLen(Eval text) { + Eval[] args = new Eval[] { text, }; + return new Len().evaluate(args, -1, (short)-1); + } + + private void confirmLen(Eval text, int expected) { + Eval result = invokeLen(text); + assertEquals(NumberEval.class, result.getClass()); + assertEquals(expected, ((NumberEval)result).getNumberValue(), 0); + } + + private void confirmLen(Eval text, ErrorEval expectedError) { + Eval result = invokeLen(text); + assertEquals(ErrorEval.class, result.getClass()); + assertEquals(expectedError.getErrorCode(), ((ErrorEval)result).getErrorCode()); + } + + public void testBasic() { + + confirmLen(new StringEval("galactic"), 8); + } + + /** + * Valid cases where text arg is not exactly a string + */ + public void testUnusualArgs() { + + // text (first) arg type is number, other args are strings with fractional digits + confirmLen(new NumberEval(123456), 6); + confirmLen(BoolEval.FALSE, 5); + confirmLen(BoolEval.TRUE, 4); + confirmLen(BlankEval.INSTANCE, 0); + } + + public void testErrors() { + confirmLen(ErrorEval.NAME_INVALID, ErrorEval.NAME_INVALID); + } +} diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestLookupFunctionsFromSpreadsheet.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestLookupFunctionsFromSpreadsheet.java new file mode 100644 index 000000000..071ca0f7d --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestLookupFunctionsFromSpreadsheet.java @@ -0,0 +1,385 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. See the NOTICE file distributed with +* this work for additional information regarding copyright ownership. +* The ASF licenses this file to You under the Apache License, Version 2.0 +* (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + + +package org.apache.poi.hssf.record.formula.functions; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.PrintStream; + +import junit.framework.Assert; +import junit.framework.AssertionFailedError; +import junit.framework.TestCase; + +import org.apache.poi.hssf.record.formula.eval.ErrorEval; +import org.apache.poi.hssf.usermodel.HSSFCell; +import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; +import org.apache.poi.hssf.usermodel.HSSFRow; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator.CellValue; +import org.apache.poi.hssf.util.CellReference; + +/** + * Tests lookup functions (VLOOKUP, HLOOKUP, LOOKUP, MATCH) as loaded from a test data spreadsheet.

    + * These tests have been separated from the common function and operator tests because the lookup + * functions have more complex test cases and test data setup. + * + * Tests for bug fixes and specific/tricky behaviour can be found in the corresponding test class + * (TestXxxx) of the target (Xxxx) implementor, where execution can be observed + * more easily. + * + * @author Josh Micich + */ +public final class TestLookupFunctionsFromSpreadsheet extends TestCase { + + private static final class Result { + public static final int SOME_EVALUATIONS_FAILED = -1; + public static final int ALL_EVALUATIONS_SUCCEEDED = +1; + public static final int NO_EVALUATIONS_FOUND = 0; + } + + /** + * This class defines constants for navigating around the test data spreadsheet used for these tests. + */ + private static final class SS { + + /** Name of the test spreadsheet (found in the standard test data folder) */ + public final static String FILENAME = "LookupFunctionsTestCaseData.xls"; + + /** Name of the first sheet in the spreadsheet (contains comments) */ + public final static String README_SHEET_NAME = "Read Me"; + + + /** Row (zero-based) in each sheet where the evaluation cases start. */ + public static final int START_TEST_CASES_ROW_INDEX = 4; // Row '5' + /** Index of the column that contains the function names */ + public static final short COLUMN_INDEX_MARKER = 0; // Column 'A' + public static final short COLUMN_INDEX_EVALUATION = 1; // Column 'B' + public static final short COLUMN_INDEX_EXPECTED_RESULT = 2; // Column 'C' + public static final short COLUMN_ROW_COMMENT = 3; // Column 'D' + + /** Used to indicate when there are no more test cases on the current sheet */ + public static final String TEST_CASES_END_MARKER = ""; + /** Used to indicate that the test on the current row should be ignored */ + public static final String SKIP_CURRENT_TEST_CASE_MARKER = ""; + + } + + // Note - multiple failures are aggregated before ending. + // If one or more functions fail, a single AssertionFailedError is thrown at the end + private int _sheetFailureCount; + private int _sheetSuccessCount; + private int _evaluationFailureCount; + private int _evaluationSuccessCount; + + + + private static void confirmExpectedResult(String msg, HSSFCell expected, HSSFFormulaEvaluator.CellValue actual) { + if (expected == null) { + throw new AssertionFailedError(msg + " - Bad setup data expected value is null"); + } + if(actual == null) { + throw new AssertionFailedError(msg + " - actual value was null"); + } + if(expected.getCellType() == HSSFCell.CELL_TYPE_ERROR) { + confirmErrorResult(msg, expected.getErrorCellValue(), actual); + return; + } + if(actual.getCellType() == HSSFCell.CELL_TYPE_ERROR) { + throw unexpectedError(msg, expected, actual.getErrorValue()); + } + if(actual.getCellType() != expected.getCellType()) { + throw wrongTypeError(msg, expected, actual); + } + + + switch (expected.getCellType()) { + case HSSFCell.CELL_TYPE_BOOLEAN: + assertEquals(msg, expected.getBooleanCellValue(), actual.getBooleanValue()); + break; + case HSSFCell.CELL_TYPE_FORMULA: // will never be used, since we will call method after formula evaluation + throw new AssertionFailedError("Cannot expect formula as result of formula evaluation: " + msg); + case HSSFCell.CELL_TYPE_NUMERIC: + assertEquals(expected.getNumericCellValue(), actual.getNumberValue(), 0.0); + break; + case HSSFCell.CELL_TYPE_STRING: + assertEquals(msg, expected.getRichStringCellValue().getString(), actual.getRichTextStringValue().getString()); + break; + } + } + + + private static AssertionFailedError wrongTypeError(String msgPrefix, HSSFCell expectedCell, CellValue actualValue) { + return new AssertionFailedError(msgPrefix + " Result type mismatch. Evaluated result was " + + formatValue(actualValue) + + " but the expected result was " + + formatValue(expectedCell) + ); + } + private static AssertionFailedError unexpectedError(String msgPrefix, HSSFCell expected, int actualErrorCode) { + return new AssertionFailedError(msgPrefix + " Error code (" + + ErrorEval.getText(actualErrorCode) + + ") was evaluated, but the expected result was " + + formatValue(expected) + ); + } + + + private static void confirmErrorResult(String msgPrefix, int expectedErrorCode, CellValue actual) { + if(actual.getCellType() != HSSFCell.CELL_TYPE_ERROR) { + throw new AssertionFailedError(msgPrefix + " Expected cell error (" + + ErrorEval.getText(expectedErrorCode) + ") but actual value was " + + formatValue(actual)); + } + if(expectedErrorCode != actual.getErrorValue()) { + throw new AssertionFailedError(msgPrefix + " Expected cell error code (" + + ErrorEval.getText(expectedErrorCode) + + ") but actual error code was (" + + ErrorEval.getText(actual.getErrorValue()) + + ")"); + } + } + + + private static String formatValue(HSSFCell expecedCell) { + switch (expecedCell.getCellType()) { + case HSSFCell.CELL_TYPE_BLANK: return ""; + case HSSFCell.CELL_TYPE_BOOLEAN: return String.valueOf(expecedCell.getBooleanCellValue()); + case HSSFCell.CELL_TYPE_NUMERIC: return String.valueOf(expecedCell.getNumericCellValue()); + case HSSFCell.CELL_TYPE_STRING: return expecedCell.getRichStringCellValue().getString(); + } + throw new RuntimeException("Unexpected cell type of expected value (" + expecedCell.getCellType() + ")"); + } + private static String formatValue(CellValue actual) { + switch (actual.getCellType()) { + case HSSFCell.CELL_TYPE_BLANK: return ""; + case HSSFCell.CELL_TYPE_BOOLEAN: return String.valueOf(actual.getBooleanValue()); + case HSSFCell.CELL_TYPE_NUMERIC: return String.valueOf(actual.getNumberValue()); + case HSSFCell.CELL_TYPE_STRING: return actual.getRichTextStringValue().getString(); + } + throw new RuntimeException("Unexpected cell type of evaluated value (" + actual.getCellType() + ")"); + } + + + protected void setUp() throws Exception { + _sheetFailureCount = 0; + _sheetSuccessCount = 0; + _evaluationFailureCount = 0; + _evaluationSuccessCount = 0; + } + + public void testFunctionsFromTestSpreadsheet() { + String filePath = System.getProperty("HSSF.testdata.path")+ "/" + SS.FILENAME; + HSSFWorkbook workbook; + try { + FileInputStream fin = new FileInputStream( filePath ); + workbook = new HSSFWorkbook( fin ); + } catch (IOException e) { + throw new RuntimeException(e); + } + + confirmReadMeSheet(workbook); + int nSheets = workbook.getNumberOfSheets(); + for(int i=1; i< nSheets; i++) { + int sheetResult = processTestSheet(workbook, i, workbook.getSheetName(i)); + switch(sheetResult) { + case Result.ALL_EVALUATIONS_SUCCEEDED: _sheetSuccessCount ++; break; + case Result.SOME_EVALUATIONS_FAILED: _sheetFailureCount ++; break; + } + } + + // confirm results + String successMsg = "There were " + + _sheetSuccessCount + " successful sheets(s) and " + + _evaluationSuccessCount + " function(s) without error"; + if(_sheetFailureCount > 0) { + String msg = _sheetFailureCount + " sheets(s) failed with " + + _evaluationFailureCount + " evaluation(s). " + successMsg; + throw new AssertionFailedError(msg); + } + if(false) { // normally no output for successful tests + System.out.println(getClass().getName() + ": " + successMsg); + } + } + + private int processTestSheet(HSSFWorkbook workbook, int sheetIndex, String sheetName) { + HSSFSheet sheet = workbook.getSheetAt(sheetIndex); + HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(sheet, workbook); + int maxRows = sheet.getLastRowNum()+1; + int result = Result.NO_EVALUATIONS_FOUND; // so far + + String currentGroupComment = null; + for(int rowIndex=SS.START_TEST_CASES_ROW_INDEX; rowIndex= endIx) { + // something went wrong. just print the whole stack trace + e.printStackTrace(ps); + } + endIx -= 4; // skip 4 frames of reflection invocation + ps.println(e.toString()); + for(int i=startIx; inull if cell is missing, empty or blank + */ + private static String getCellTextValue(HSSFRow r, int colIndex, String columnName) { + if(r == null) { + return null; + } + HSSFCell cell = r.getCell((short) colIndex); + if(cell == null) { + return null; + } + if(cell.getCellType() == HSSFCell.CELL_TYPE_BLANK) { + return null; + } + if(cell.getCellType() == HSSFCell.CELL_TYPE_STRING) { + return cell.getRichStringCellValue().getString(); + } + + throw new RuntimeException("Bad cell type for '" + columnName + "' column: (" + + cell.getCellType() + ") row (" + (r.getRowNum() +1) + ")"); + } +} diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestMatch.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestMatch.java new file mode 100755 index 000000000..d275e5f33 --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestMatch.java @@ -0,0 +1,215 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hssf.record.formula.functions; + +import junit.framework.TestCase; + +import org.apache.poi.hssf.record.formula.AreaPtg; +import org.apache.poi.hssf.record.formula.eval.Area2DEval; +import org.apache.poi.hssf.record.formula.eval.AreaEval; +import org.apache.poi.hssf.record.formula.eval.BoolEval; +import org.apache.poi.hssf.record.formula.eval.ErrorEval; +import org.apache.poi.hssf.record.formula.eval.Eval; +import org.apache.poi.hssf.record.formula.eval.NumberEval; +import org.apache.poi.hssf.record.formula.eval.NumericValueEval; +import org.apache.poi.hssf.record.formula.eval.StringEval; +import org.apache.poi.hssf.record.formula.eval.ValueEval; + +/** + * Test cases for MATCH() + * + * @author Josh Micich + */ +public final class TestMatch extends TestCase { + /** less than or equal to */ + private static final NumberEval MATCH_LARGEST_LTE = new NumberEval(1); + private static final NumberEval MATCH_EXACT = new NumberEval(0); + /** greater than or equal to */ + private static final NumberEval MATCH_SMALLEST_GTE = new NumberEval(-1); + + + private static Eval invokeMatch(Eval lookup_value, Eval lookup_array, Eval match_type) { + Eval[] args = { lookup_value, lookup_array, match_type, }; + return new Match().evaluate(args, -1, (short)-1); + } + private static void confirmInt(int expected, Eval actualEval) { + if(!(actualEval instanceof NumericValueEval)) { + fail("Expected numeric result"); + } + NumericValueEval nve = (NumericValueEval)actualEval; + assertEquals(expected, nve.getNumberValue(), 0); + } + /** + * Convenience method + * @return new Area2DEval(new AreaPtg(ref), values) + */ + private static AreaEval createAreaEval(String ref, ValueEval[] values) { + return new Area2DEval(new AreaPtg(ref), values); + } + + public void testSimpleNumber() { + + ValueEval[] values = { + new NumberEval(4), + new NumberEval(5), + new NumberEval(10), + new NumberEval(10), + new NumberEval(25), + }; + + AreaEval ae = createAreaEval("A1:A5", values); + + confirmInt(2, invokeMatch(new NumberEval(5), ae, MATCH_LARGEST_LTE)); + confirmInt(2, invokeMatch(new NumberEval(5), ae, MATCH_EXACT)); + confirmInt(4, invokeMatch(new NumberEval(10), ae, MATCH_LARGEST_LTE)); + confirmInt(3, invokeMatch(new NumberEval(10), ae, MATCH_EXACT)); + confirmInt(4, invokeMatch(new NumberEval(20), ae, MATCH_LARGEST_LTE)); + assertEquals(ErrorEval.NA, invokeMatch(new NumberEval(20), ae, MATCH_EXACT)); + } + + public void testReversedNumber() { + + ValueEval[] values = { + new NumberEval(25), + new NumberEval(10), + new NumberEval(10), + new NumberEval(10), + new NumberEval(4), + }; + + AreaEval ae = createAreaEval("A1:A5", values); + + confirmInt(2, invokeMatch(new NumberEval(10), ae, MATCH_SMALLEST_GTE)); + confirmInt(2, invokeMatch(new NumberEval(10), ae, MATCH_EXACT)); + confirmInt(4, invokeMatch(new NumberEval(9), ae, MATCH_SMALLEST_GTE)); + confirmInt(1, invokeMatch(new NumberEval(20), ae, MATCH_SMALLEST_GTE)); + assertEquals(ErrorEval.NA, invokeMatch(new NumberEval(20), ae, MATCH_EXACT)); + assertEquals(ErrorEval.NA, invokeMatch(new NumberEval(26), ae, MATCH_SMALLEST_GTE)); + } + + public void testSimpleString() { + + ValueEval[] values = { + new StringEval("Albert"), + new StringEval("Charles"), + new StringEval("Ed"), + new StringEval("Greg"), + new StringEval("Ian"), + }; + + AreaEval ae = createAreaEval("A1:A5", values); + + // Note String comparisons are case insensitive + confirmInt(3, invokeMatch(new StringEval("Ed"), ae, MATCH_LARGEST_LTE)); + confirmInt(3, invokeMatch(new StringEval("eD"), ae, MATCH_LARGEST_LTE)); + confirmInt(3, invokeMatch(new StringEval("Ed"), ae, MATCH_EXACT)); + confirmInt(3, invokeMatch(new StringEval("ed"), ae, MATCH_EXACT)); + confirmInt(4, invokeMatch(new StringEval("Hugh"), ae, MATCH_LARGEST_LTE)); + assertEquals(ErrorEval.NA, invokeMatch(new StringEval("Hugh"), ae, MATCH_EXACT)); + } + + public void testSimpleBoolean() { + + ValueEval[] values = { + BoolEval.FALSE, + BoolEval.FALSE, + BoolEval.TRUE, + BoolEval.TRUE, + }; + + AreaEval ae = createAreaEval("A1:A4", values); + + // Note String comparisons are case insensitive + confirmInt(2, invokeMatch(BoolEval.FALSE, ae, MATCH_LARGEST_LTE)); + confirmInt(1, invokeMatch(BoolEval.FALSE, ae, MATCH_EXACT)); + confirmInt(4, invokeMatch(BoolEval.TRUE, ae, MATCH_LARGEST_LTE)); + confirmInt(3, invokeMatch(BoolEval.TRUE, ae, MATCH_EXACT)); + } + + public void testHeterogeneous() { + + ValueEval[] values = { + new NumberEval(4), + BoolEval.FALSE, + new NumberEval(5), + new StringEval("Albert"), + BoolEval.FALSE, + BoolEval.TRUE, + new NumberEval(10), + new StringEval("Charles"), + new StringEval("Ed"), + new NumberEval(10), + new NumberEval(25), + BoolEval.TRUE, + new StringEval("Ed"), + }; + + AreaEval ae = createAreaEval("A1:A13", values); + + assertEquals(ErrorEval.NA, invokeMatch(new StringEval("Aaron"), ae, MATCH_LARGEST_LTE)); + + confirmInt(5, invokeMatch(BoolEval.FALSE, ae, MATCH_LARGEST_LTE)); + confirmInt(2, invokeMatch(BoolEval.FALSE, ae, MATCH_EXACT)); + confirmInt(3, invokeMatch(new NumberEval(5), ae, MATCH_LARGEST_LTE)); + confirmInt(3, invokeMatch(new NumberEval(5), ae, MATCH_EXACT)); + + confirmInt(8, invokeMatch(new StringEval("CHARLES"), ae, MATCH_EXACT)); + + confirmInt(4, invokeMatch(new StringEval("Ben"), ae, MATCH_LARGEST_LTE)); + + confirmInt(13, invokeMatch(new StringEval("ED"), ae, MATCH_LARGEST_LTE)); + confirmInt(9, invokeMatch(new StringEval("ED"), ae, MATCH_EXACT)); + + confirmInt(13, invokeMatch(new StringEval("Hugh"), ae, MATCH_LARGEST_LTE)); + assertEquals(ErrorEval.NA, invokeMatch(new StringEval("Hugh"), ae, MATCH_EXACT)); + + confirmInt(11, invokeMatch(new NumberEval(30), ae, MATCH_LARGEST_LTE)); + confirmInt(12, invokeMatch(BoolEval.TRUE, ae, MATCH_LARGEST_LTE)); + } + + + /** + * Ensures that the match_type argument can be an AreaEval.
    + * Bugzilla 44421 + */ + public void testMatchArgTypeArea() { + + ValueEval[] values = { + new NumberEval(4), + new NumberEval(5), + new NumberEval(10), + new NumberEval(10), + new NumberEval(25), + }; + + AreaEval ae = createAreaEval("A1:A5", values); + + AreaEval matchAE = createAreaEval("C1:C1", new ValueEval[] { MATCH_LARGEST_LTE, }); + + try { + confirmInt(4, invokeMatch(new NumberEval(10), ae, matchAE)); + } catch (RuntimeException e) { + if(e.getMessage().startsWith("Unexpected match_type type")) { + // identified bug 44421 + fail(e.getMessage()); + } + // some other error ?? + throw e; + } + } +} diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestMid.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestMid.java new file mode 100755 index 000000000..dc3d595ae --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestMid.java @@ -0,0 +1,115 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hssf.record.formula.functions; + +import org.apache.poi.hssf.record.formula.AreaPtg; +import org.apache.poi.hssf.record.formula.ReferencePtg; +import org.apache.poi.hssf.record.formula.eval.Area2DEval; +import org.apache.poi.hssf.record.formula.eval.AreaEval; +import org.apache.poi.hssf.record.formula.eval.BlankEval; +import org.apache.poi.hssf.record.formula.eval.BoolEval; +import org.apache.poi.hssf.record.formula.eval.ErrorEval; +import org.apache.poi.hssf.record.formula.eval.Eval; +import org.apache.poi.hssf.record.formula.eval.NumberEval; +import org.apache.poi.hssf.record.formula.eval.Ref2DEval; +import org.apache.poi.hssf.record.formula.eval.RefEval; +import org.apache.poi.hssf.record.formula.eval.StringEval; +import org.apache.poi.hssf.record.formula.eval.ValueEval; + +import junit.framework.TestCase; +/** + * Tests for Excel function MID() + * + * @author Josh Micich + */ +public final class TestMid extends TestCase { + + + private static Eval invokeMid(Eval text, Eval startPos, Eval numChars) { + Eval[] args = new Eval[] { text, startPos, numChars, }; + return new Mid().evaluate(args, -1, (short)-1); + } + + private void confirmMid(Eval text, Eval startPos, Eval numChars, String expected) { + Eval result = invokeMid(text, startPos, numChars); + assertEquals(StringEval.class, result.getClass()); + assertEquals(expected, ((StringEval)result).getStringValue()); + } + + private void confirmMid(Eval text, Eval startPos, Eval numChars, ErrorEval expectedError) { + Eval result = invokeMid(text, startPos, numChars); + assertEquals(ErrorEval.class, result.getClass()); + assertEquals(expectedError.getErrorCode(), ((ErrorEval)result).getErrorCode()); + } + + public void testBasic() { + + confirmMid(new StringEval("galactic"), new NumberEval(3), new NumberEval(4), "lact"); + } + + /** + * Valid cases where args are not precisely (string, int, int) but can be resolved OK. + */ + public void testUnusualArgs() { + // startPos with fractional digits + confirmMid(new StringEval("galactic"), new NumberEval(3.1), new NumberEval(4), "lact"); + + // string startPos + confirmMid(new StringEval("galactic"), new StringEval("3"), new NumberEval(4), "lact"); + + // text (first) arg type is number, other args are strings with fractional digits + confirmMid(new NumberEval(123456), new StringEval("3.1"), new StringEval("2.9"), "34"); + + // startPos is 1x1 area ref, numChars is cell ref + AreaEval aeStart = new Area2DEval(new AreaPtg("A1:A1"), new ValueEval[] { new NumberEval(2), } ); + RefEval reNumChars = new Ref2DEval(new ReferencePtg("B1"), new NumberEval(3)); + confirmMid(new StringEval("galactic"), aeStart, reNumChars, "ala"); + + confirmMid(new StringEval("galactic"), new NumberEval(3.1), BlankEval.INSTANCE, ""); + + confirmMid(new StringEval("galactic"), new NumberEval(3), BoolEval.FALSE, ""); + confirmMid(new StringEval("galactic"), new NumberEval(3), BoolEval.TRUE, "l"); + confirmMid(BlankEval.INSTANCE, new NumberEval(3), BoolEval.TRUE, ""); + + } + + /** + * Extreme values for startPos and numChars + */ + public void testExtremes() { + confirmMid(new StringEval("galactic"), new NumberEval(4), new NumberEval(400), "actic"); + + confirmMid(new StringEval("galactic"), new NumberEval(30), new NumberEval(4), ""); + confirmMid(new StringEval("galactic"), new NumberEval(3), new NumberEval(0), ""); + } + + /** + * All sorts of ways to make MID return defined errors. + */ + public void testErrors() { + confirmMid(ErrorEval.NAME_INVALID, new NumberEval(3), new NumberEval(4), ErrorEval.NAME_INVALID); + confirmMid(new StringEval("galactic"), ErrorEval.NAME_INVALID, new NumberEval(4), ErrorEval.NAME_INVALID); + confirmMid(new StringEval("galactic"), new NumberEval(3), ErrorEval.NAME_INVALID, ErrorEval.NAME_INVALID); + confirmMid(new StringEval("galactic"), ErrorEval.DIV_ZERO, ErrorEval.NAME_INVALID, ErrorEval.DIV_ZERO); + + confirmMid(new StringEval("galactic"), BlankEval.INSTANCE, new NumberEval(3.1), ErrorEval.VALUE_INVALID); + + confirmMid(new StringEval("galactic"), new NumberEval(0), new NumberEval(4), ErrorEval.VALUE_INVALID); + confirmMid(new StringEval("galactic"), new NumberEval(1), new NumberEval(-1), ErrorEval.VALUE_INVALID); + } +} diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestOffset.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestOffset.java new file mode 100755 index 000000000..f11466298 --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestOffset.java @@ -0,0 +1,92 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hssf.record.formula.functions; + +import junit.framework.TestCase; + +import org.apache.poi.hssf.record.formula.functions.Offset.LinearOffsetRange; + +/** + * Tests for OFFSET function implementation + * + * @author Josh Micich + */ +public final class TestOffset extends TestCase { + + + private static void confirmDoubleConvert(double doubleVal, int expected) { + assertEquals(expected, Offset.convertDoubleToInt(doubleVal)); + } + /** + * Excel's double to int conversion (for function 'OFFSET()') behaves more like Math.floor(). + * Note - negative values are not symmetrical + */ + public void testDoubleConversion() { + + confirmDoubleConvert(100.09, 100); + confirmDoubleConvert(100.01, 100); + confirmDoubleConvert(100.00, 100); + confirmDoubleConvert(99.99, 99); + + confirmDoubleConvert(+2.01, +2); + confirmDoubleConvert(+2.00, +2); + confirmDoubleConvert(+1.99, +1); + confirmDoubleConvert(+1.01, +1); + confirmDoubleConvert(+1.00, +1); + confirmDoubleConvert(+0.99, 0); + confirmDoubleConvert(+0.01, 0); + confirmDoubleConvert( 0.00, 0); + confirmDoubleConvert(-0.01, -1); + confirmDoubleConvert(-0.99, -1); + confirmDoubleConvert(-1.00, -1); + confirmDoubleConvert(-1.01, -2); + confirmDoubleConvert(-1.99, -2); + confirmDoubleConvert(-2.00, -2); + confirmDoubleConvert(-2.01, -3); + } + + public void testLinearOffsetRange() { + LinearOffsetRange lor; + + lor = new LinearOffsetRange(3, 2); + assertEquals(3, lor.getFirstIndex()); + assertEquals(4, lor.getLastIndex()); + lor = lor.normaliseAndTranslate(0); // expected no change + assertEquals(3, lor.getFirstIndex()); + assertEquals(4, lor.getLastIndex()); + + lor = lor.normaliseAndTranslate(5); + assertEquals(8, lor.getFirstIndex()); + assertEquals(9, lor.getLastIndex()); + + // negative length + + lor = new LinearOffsetRange(6, -4).normaliseAndTranslate(0); + assertEquals(3, lor.getFirstIndex()); + assertEquals(6, lor.getLastIndex()); + + + // bounds checking + lor = new LinearOffsetRange(0, 100); + assertFalse(lor.isOutOfBounds(0, 16383)); + lor = lor.normaliseAndTranslate(16300); + assertTrue(lor.isOutOfBounds(0, 16383)); + assertFalse(lor.isOutOfBounds(0, 65535)); + } + +} diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestRoundFuncs.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestRoundFuncs.java new file mode 100755 index 000000000..a6ce345ae --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestRoundFuncs.java @@ -0,0 +1,49 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hssf.record.formula.functions; + +import org.apache.poi.hssf.record.formula.eval.ErrorEval; +import org.apache.poi.hssf.record.formula.eval.Eval; +import org.apache.poi.hssf.record.formula.eval.NumberEval; +import org.apache.poi.hssf.record.formula.eval.StringEval; + +import junit.framework.TestCase; + +/** + * Test cases for ROUND(), ROUNDUP(), ROUNDDOWN() + * + * @author Josh Micich + */ +public final class TestRoundFuncs extends TestCase { + public void testRounddownWithStringArg() { + + Eval strArg = new StringEval("abc"); + Eval[] args = { strArg, new NumberEval(2), }; + Eval result = new Rounddown().evaluate(args, -1, (short)-1); + assertEquals(ErrorEval.VALUE_INVALID, result); + } + + public void testRoundupWithStringArg() { + + Eval strArg = new StringEval("abc"); + Eval[] args = { strArg, new NumberEval(2), }; + Eval result = new Roundup().evaluate(args, -1, (short)-1); + assertEquals(ErrorEval.VALUE_INVALID, result); + } + +} diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestSumproduct.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestSumproduct.java new file mode 100755 index 000000000..73043911f --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestSumproduct.java @@ -0,0 +1,120 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hssf.record.formula.functions; + +import org.apache.poi.hssf.record.formula.ReferencePtg; +import org.apache.poi.hssf.record.formula.eval.AreaEval; +import org.apache.poi.hssf.record.formula.eval.ErrorEval; +import org.apache.poi.hssf.record.formula.eval.Eval; +import org.apache.poi.hssf.record.formula.eval.NumberEval; +import org.apache.poi.hssf.record.formula.eval.NumericValueEval; +import org.apache.poi.hssf.record.formula.eval.Ref2DEval; +import org.apache.poi.hssf.record.formula.eval.RefEval; +import org.apache.poi.hssf.record.formula.eval.ValueEval; + +import junit.framework.TestCase; + +/** + * Test cases for SUMPRODUCT() + * + * @author Josh Micich + */ +public final class TestSumproduct extends TestCase { + + private static Eval invokeSumproduct(Eval[] args) { + // srcCellRow and srcCellColumn are ignored by SUMPRODUCT + return new Sumproduct().evaluate(args, -1, (short)-1); + } + private static void confirmDouble(double expected, Eval actualEval) { + if(!(actualEval instanceof NumericValueEval)) { + fail("Expected numeric result"); + } + NumericValueEval nve = (NumericValueEval)actualEval; + assertEquals(expected, nve.getNumberValue(), 0); + } + + public void testScalarSimple() { + + RefEval refEval = new Ref2DEval(new ReferencePtg("A1"), new NumberEval(3)); + Eval[] args = { + refEval, + new NumberEval(2), + }; + Eval result = invokeSumproduct(args); + confirmDouble(6D, result); + } + + + public void testAreaSimple() { + + AreaEval aeA = EvalFactory.createAreaEval("A1:A3", 1, 3); + AreaEval aeB = EvalFactory.createAreaEval("B1:B3", 1, 3); + ValueEval[] aValues = aeA.getValues(); + ValueEval[] bValues = aeB.getValues(); + aValues[0] = new NumberEval(2); + aValues[1] = new NumberEval(4); + aValues[2] = new NumberEval(5); + bValues[0] = new NumberEval(3); + bValues[1] = new NumberEval(6); + bValues[2] = new NumberEval(7); + + Eval[] args = { aeA, aeB, }; + Eval result = invokeSumproduct(args); + confirmDouble(65D, result); + } + + /** + * For scalar products, the terms may be 1x1 area refs + */ + public void testOneByOneArea() { + + AreaEval ae = EvalFactory.createAreaEval("A1:A1", 1, 1); + ae.getValues()[0] = new NumberEval(7); + + Eval[] args = { + ae, + new NumberEval(2), + }; + Eval result = invokeSumproduct(args); + confirmDouble(14D, result); + } + + + public void testMismatchAreaDimensions() { + + AreaEval aeA = EvalFactory.createAreaEval("A1:A3", 1, 3); + AreaEval aeB = EvalFactory.createAreaEval("B1:D1", 3, 1); + + Eval[] args; + args = new Eval[] { aeA, aeB, }; + assertEquals(ErrorEval.VALUE_INVALID, invokeSumproduct(args)); + + args = new Eval[] { aeA, new NumberEval(5), }; + assertEquals(ErrorEval.VALUE_INVALID, invokeSumproduct(args)); + } + + public void testAreaWithErrorCell() { + AreaEval aeA = EvalFactory.createAreaEval("A1:A2", 1, 2); + AreaEval aeB = EvalFactory.createAreaEval("B1:B2", 1, 2); + aeB.getValues()[1] = ErrorEval.REF_INVALID; + + Eval[] args = { aeA, aeB, }; + assertEquals(ErrorEval.REF_INVALID, invokeSumproduct(args)); + } + +} diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestTFunc.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestTFunc.java new file mode 100755 index 000000000..4d63cad1c --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestTFunc.java @@ -0,0 +1,118 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hssf.record.formula.functions; + +import org.apache.poi.hssf.record.formula.ReferencePtg; +import org.apache.poi.hssf.record.formula.eval.BlankEval; +import org.apache.poi.hssf.record.formula.eval.BoolEval; +import org.apache.poi.hssf.record.formula.eval.ErrorEval; +import org.apache.poi.hssf.record.formula.eval.Eval; +import org.apache.poi.hssf.record.formula.eval.NumberEval; +import org.apache.poi.hssf.record.formula.eval.Ref2DEval; +import org.apache.poi.hssf.record.formula.eval.StringEval; +import org.apache.poi.hssf.record.formula.eval.ValueEval; + +import junit.framework.TestCase; + +/** + * Test cases for Excel function T() + * + * @author Josh Micich + */ +public final class TestTFunc extends TestCase { + + /** + * @return the result of calling function T() with the specified argument + */ + private static Eval invokeT(Eval arg) { + Eval[] args = { arg, }; + Eval result = new T().evaluate(args, -1, (short)-1); + assertNotNull("result may never be null", result); + return result; + } + /** + * Simulates call: T(A1) + * where cell A1 has the specified innerValue + */ + private Eval invokeTWithReference(ValueEval innerValue) { + Eval arg = new Ref2DEval(new ReferencePtg((short)1, (short)1, false, false), innerValue); + return invokeT(arg); + } + + private static void confirmText(String text) { + Eval arg = new StringEval(text); + Eval eval = invokeT(arg); + StringEval se = (StringEval) eval; + assertEquals(text, se.getStringValue()); + } + + public void testTextValues() { + + confirmText("abc"); + confirmText(""); + confirmText(" "); + confirmText("~"); + confirmText("123"); + confirmText("TRUE"); + } + + private static void confirmError(Eval arg) { + Eval eval = invokeT(arg); + assertTrue(arg == eval); + } + + public void testErrorValues() { + + confirmError(ErrorEval.VALUE_INVALID); + confirmError(ErrorEval.NA); + confirmError(ErrorEval.REF_INVALID); + } + + private static void confirmString(Eval eval, String expected) { + assertTrue(eval instanceof StringEval); + assertEquals(expected, ((StringEval)eval).getStringValue()); + } + + private static void confirmOther(Eval arg) { + Eval eval = invokeT(arg); + confirmString(eval, ""); + } + + public void testOtherValues() { + confirmOther(new NumberEval(2)); + confirmOther(BoolEval.FALSE); + confirmOther(BlankEval.INSTANCE); // can this particular case be verified? + } + + public void testRefValues() { + Eval eval; + + eval = invokeTWithReference(new StringEval("def")); + confirmString(eval, "def"); + eval = invokeTWithReference(new StringEval(" ")); + confirmString(eval, " "); + + eval = invokeTWithReference(new NumberEval(2)); + confirmString(eval, ""); + eval = invokeTWithReference(BoolEval.TRUE); + confirmString(eval, ""); + + eval = invokeTWithReference(ErrorEval.NAME_INVALID); + assertTrue(eval == ErrorEval.NAME_INVALID); + } +} diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestTrim.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestTrim.java new file mode 100755 index 000000000..076ac1fc7 --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestTrim.java @@ -0,0 +1,78 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hssf.record.formula.functions; + +import junit.framework.TestCase; + +import org.apache.poi.hssf.record.formula.eval.BlankEval; +import org.apache.poi.hssf.record.formula.eval.BoolEval; +import org.apache.poi.hssf.record.formula.eval.ErrorEval; +import org.apache.poi.hssf.record.formula.eval.Eval; +import org.apache.poi.hssf.record.formula.eval.NumberEval; +import org.apache.poi.hssf.record.formula.eval.StringEval; +/** + * Tests for Excel function TRIM() + * + * @author Josh Micich + */ +public final class TestTrim extends TestCase { + + + private static Eval invokeTrim(Eval text) { + Eval[] args = new Eval[] { text, }; + return new Trim().evaluate(args, -1, (short)-1); + } + + private void confirmTrim(Eval text, String expected) { + Eval result = invokeTrim(text); + assertEquals(StringEval.class, result.getClass()); + assertEquals(expected, ((StringEval)result).getStringValue()); + } + + private void confirmTrim(Eval text, ErrorEval expectedError) { + Eval result = invokeTrim(text); + assertEquals(ErrorEval.class, result.getClass()); + assertEquals(expectedError.getErrorCode(), ((ErrorEval)result).getErrorCode()); + } + + public void testBasic() { + + confirmTrim(new StringEval(" hi "), "hi"); + confirmTrim(new StringEval("hi "), "hi"); + confirmTrim(new StringEval(" hi"), "hi"); + confirmTrim(new StringEval(" hi there "), "hi there"); + confirmTrim(new StringEval(""), ""); + confirmTrim(new StringEval(" "), ""); + } + + /** + * Valid cases where text arg is not exactly a string + */ + public void testUnusualArgs() { + + // text (first) arg type is number, other args are strings with fractional digits + confirmTrim(new NumberEval(123456), "123456"); + confirmTrim(BoolEval.FALSE, "FALSE"); + confirmTrim(BoolEval.TRUE, "TRUE"); + confirmTrim(BlankEval.INSTANCE, ""); + } + + public void testErrors() { + confirmTrim(ErrorEval.NAME_INVALID, ErrorEval.NAME_INVALID); + } +} diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestXYNumericFunction.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestXYNumericFunction.java new file mode 100755 index 000000000..c9f043bd3 --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestXYNumericFunction.java @@ -0,0 +1,139 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hssf.record.formula.functions; + +import junit.framework.TestCase; + +import org.apache.poi.hssf.record.formula.AreaPtg; +import org.apache.poi.hssf.record.formula.eval.Area2DEval; +import org.apache.poi.hssf.record.formula.eval.ErrorEval; +import org.apache.poi.hssf.record.formula.eval.Eval; +import org.apache.poi.hssf.record.formula.eval.NumberEval; +import org.apache.poi.hssf.record.formula.eval.ValueEval; +/** + * Tests for Excel functions SUMX2MY2(), SUMX2PY2(), SUMXMY2() + * + * @author Josh Micich + */ +public final class TestXYNumericFunction extends TestCase { + private static final Function SUM_SQUARES = new Sumx2py2(); + private static final Function DIFF_SQUARES = new Sumx2my2(); + private static final Function SUM_SQUARES_OF_DIFFS = new Sumxmy2(); + + private static Eval invoke(Function function, Eval xArray, Eval yArray) { + Eval[] args = new Eval[] { xArray, yArray, }; + return function.evaluate(args, -1, (short)-1); + } + + private void confirm(Function function, Eval xArray, Eval yArray, double expected) { + Eval result = invoke(function, xArray, yArray); + assertEquals(NumberEval.class, result.getClass()); + assertEquals(expected, ((NumberEval)result).getNumberValue(), 0); + } + private void confirmError(Function function, Eval xArray, Eval yArray, ErrorEval expectedError) { + Eval result = invoke(function, xArray, yArray); + assertEquals(ErrorEval.class, result.getClass()); + assertEquals(expectedError.getErrorCode(), ((ErrorEval)result).getErrorCode()); + } + + private void confirmError(Eval xArray, Eval yArray, ErrorEval expectedError) { + confirmError(SUM_SQUARES, xArray, yArray, expectedError); + confirmError(DIFF_SQUARES, xArray, yArray, expectedError); + confirmError(SUM_SQUARES_OF_DIFFS, xArray, yArray, expectedError); + } + + public void testBasic() { + ValueEval[] xValues = { + new NumberEval(1), + new NumberEval(2), + }; + ValueEval areaEvalX = createAreaEval(xValues); + confirm(SUM_SQUARES, areaEvalX, areaEvalX, 10.0); + confirm(DIFF_SQUARES, areaEvalX, areaEvalX, 0.0); + confirm(SUM_SQUARES_OF_DIFFS, areaEvalX, areaEvalX, 0.0); + + ValueEval[] yValues = { + new NumberEval(3), + new NumberEval(4), + }; + ValueEval areaEvalY = createAreaEval(yValues); + confirm(SUM_SQUARES, areaEvalX, areaEvalY, 30.0); + confirm(DIFF_SQUARES, areaEvalX, areaEvalY, -20.0); + confirm(SUM_SQUARES_OF_DIFFS, areaEvalX, areaEvalY, 8.0); + } + + /** + * number of items in array is not limited to 30 + */ + public void testLargeArrays() { + ValueEval[] xValues = createMockNumberArray(100, 3); + ValueEval[] yValues = createMockNumberArray(100, 2); + + confirm(SUM_SQUARES, createAreaEval(xValues), createAreaEval(yValues), 1300.0); + confirm(DIFF_SQUARES, createAreaEval(xValues), createAreaEval(yValues), 500.0); + confirm(SUM_SQUARES_OF_DIFFS, createAreaEval(xValues), createAreaEval(yValues), 100.0); + } + + + private ValueEval[] createMockNumberArray(int size, double value) { + ValueEval[] result = new ValueEval[size]; + for (int i = 0; i < result.length; i++) { + result[i] = new NumberEval(value); + } + return result; + } + + private static ValueEval createAreaEval(ValueEval[] values) { + String refStr = "A1:A" + values.length; + return new Area2DEval(new AreaPtg(refStr), values); + } + + public void testErrors() { + ValueEval[] xValues = { + ErrorEval.REF_INVALID, + new NumberEval(2), + }; + ValueEval areaEvalX = createAreaEval(xValues); + ValueEval[] yValues = { + new NumberEval(2), + ErrorEval.NULL_INTERSECTION, + }; + ValueEval areaEvalY = createAreaEval(yValues); + ValueEval[] zValues = { // wrong size + new NumberEval(2), + }; + ValueEval areaEvalZ = createAreaEval(zValues); + + // if either arg is an error, that error propagates + confirmError(ErrorEval.REF_INVALID, ErrorEval.NAME_INVALID, ErrorEval.REF_INVALID); + confirmError(areaEvalX, ErrorEval.NAME_INVALID, ErrorEval.NAME_INVALID); + confirmError(ErrorEval.NAME_INVALID, areaEvalX, ErrorEval.NAME_INVALID); + + // array sizes must match + confirmError(areaEvalX, areaEvalZ, ErrorEval.NA); + confirmError(areaEvalZ, areaEvalY, ErrorEval.NA); + + // any error in an array item propagates up + confirmError(areaEvalX, areaEvalX, ErrorEval.REF_INVALID); + + // search for errors array by array, not pair by pair + confirmError(areaEvalX, areaEvalY, ErrorEval.REF_INVALID); + confirmError(areaEvalY, areaEvalX, ErrorEval.NULL_INTERSECTION); + + } +} diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestBug42464.java b/src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestBug42464.java index 3b31cc03a..c849fd436 100644 --- a/src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestBug42464.java +++ b/src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestBug42464.java @@ -21,14 +21,14 @@ import java.io.FileInputStream; import java.util.Iterator; import java.util.List; -import org.apache.poi.hssf.record.FormulaRecord; -import org.apache.poi.hssf.record.aggregates.FormulaRecordAggregate; -import org.apache.poi.hssf.record.formula.ExpPtg; -import org.apache.poi.hssf.util.CellReference; - import junit.framework.TestCase; -public class TestBug42464 extends TestCase { +import org.apache.poi.hssf.record.FormulaRecord; +import org.apache.poi.hssf.record.aggregates.FormulaRecordAggregate; +import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator.CellValue; +import org.apache.poi.hssf.util.CellReference; + +public final class TestBug42464 extends TestCase { String dirname; protected void setUp() throws Exception { @@ -68,26 +68,27 @@ public class TestBug42464 extends TestCase { Iterator it = row.cellIterator(); while(it.hasNext()) { HSSFCell cell = (HSSFCell)it.next(); - if(cell.getCellType() == HSSFCell.CELL_TYPE_FORMULA) { - FormulaRecordAggregate record = (FormulaRecordAggregate) - cell.getCellValueRecord(); - FormulaRecord r = record.getFormulaRecord(); - List ptgs = r.getParsedExpression(); - - String cellRef = (new CellReference(row.getRowNum(), cell.getCellNum())).toString(); - if(cellRef.equals("BP24")) { - System.out.print(cellRef); - System.out.println(" - has " + r.getNumberOfExpressionTokens() + " ptgs over " + r.getExpressionLength() + " tokens:"); - for(int i=0; i " + cell.getCellFormula()); - } - - eval.evaluate(cell); - + if(cell.getCellType() != HSSFCell.CELL_TYPE_FORMULA) { + continue; } + FormulaRecordAggregate record = (FormulaRecordAggregate) cell.getCellValueRecord(); + FormulaRecord r = record.getFormulaRecord(); + List ptgs = r.getParsedExpression(); + + String cellRef = new CellReference(row.getRowNum(), cell.getCellNum(), false, false).formatAsString(); + if(false && cellRef.equals("BP24")) { // TODO - replace System.out.println()s with asserts + System.out.print(cellRef); + System.out.println(" - has " + r.getNumberOfExpressionTokens() + + " ptgs over " + r.getExpressionLength() + " tokens:"); + for(int i=0; i " + cell.getCellFormula()); + } + + CellValue evalResult = eval.evaluate(cell); + assertNotNull(evalResult); } } } diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestBug44410.java b/src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestBug44410.java new file mode 100644 index 000000000..27c3bdc38 --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestBug44410.java @@ -0,0 +1,100 @@ +package org.apache.poi.hssf.usermodel; +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +import junit.framework.TestCase; + +import java.io.IOException; +import java.io.FileInputStream; +import java.io.File; +import java.util.List; + +import org.apache.poi.hssf.record.aggregates.FormulaRecordAggregate; +import org.apache.poi.hssf.record.formula.AreaPtg; +import org.apache.poi.hssf.record.formula.FuncVarPtg; + +/** + * Bug 44410: SUM(C:C) is valid in excel, and means a sum + * of all the rows in Column C + * + * @author Nick Burch + */ + +public class TestBug44410 extends TestCase { + protected String cwd = System.getProperty("HSSF.testdata.path"); + + public void test44410() throws IOException { + FileInputStream in = new FileInputStream(new File(cwd, "SingleLetterRanges.xls")); + HSSFWorkbook wb = new HSSFWorkbook(in); + in.close(); + + HSSFSheet sheet = wb.getSheetAt(0); + + HSSFFormulaEvaluator eva = new HSSFFormulaEvaluator(sheet, wb); + + // =index(C:C,2,1) -> 2 + HSSFRow rowIDX = (HSSFRow)sheet.getRow(3); + // =sum(C:C) -> 6 + HSSFRow rowSUM = (HSSFRow)sheet.getRow(4); + // =sum(C:D) -> 66 + HSSFRow rowSUM2D = (HSSFRow)sheet.getRow(5); + + // Test the sum + HSSFCell cellSUM = rowSUM.getCell((short)0); + + FormulaRecordAggregate frec = + (FormulaRecordAggregate)cellSUM.getCellValueRecord(); + List ops = frec.getFormulaRecord().getParsedExpression(); + assertEquals(2, ops.size()); + assertEquals(AreaPtg.class, ops.get(0).getClass()); + assertEquals(FuncVarPtg.class, ops.get(1).getClass()); + + // Actually stored as C1 to C65536 + // (last row is -1 === 65535) + AreaPtg ptg = (AreaPtg)ops.get(0); + assertEquals(2, ptg.getFirstColumn()); + assertEquals(2, ptg.getLastColumn()); + assertEquals(0, ptg.getFirstRow()); + assertEquals(65535, ptg.getLastRow()); + assertEquals("C:C", ptg.toFormulaString(wb.getWorkbook())); + + // Will show as C:C, but won't know how many + // rows it covers as we don't have the sheet + // to hand when turning the Ptgs into a string + assertEquals("SUM(C:C)", cellSUM.getCellFormula()); + eva.setCurrentRow(rowSUM); + + // But the evaluator knows the sheet, so it + // can do it properly + assertEquals(6, eva.evaluate(cellSUM).getNumberValue(), 0); + + + // Test the index + // Again, the formula string will be right but + // lacking row count, evaluated will be right + HSSFCell cellIDX = rowIDX.getCell((short)0); + assertEquals("INDEX(C:C,2,1)", cellIDX.getCellFormula()); + eva.setCurrentRow(rowIDX); + assertEquals(2, eva.evaluate(cellIDX).getNumberValue(), 0); + + // Across two colums + HSSFCell cellSUM2D = rowSUM2D.getCell((short)0); + assertEquals("SUM(C:D)", cellSUM2D.getCellFormula()); + eva.setCurrentRow(rowSUM2D); + assertEquals(66, eva.evaluate(cellSUM2D).getNumberValue(), 0); + } +} diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestBug44508.java b/src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestBug44508.java new file mode 100644 index 000000000..3362f3c3e --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestBug44508.java @@ -0,0 +1,42 @@ +package org.apache.poi.hssf.usermodel; +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +import junit.framework.TestCase; + +public class TestBug44508 extends TestCase { + protected String cwd = System.getProperty("HSSF.testdata.path"); + + public void testEvaluateBooleanInCell_bug44508() { + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sheet = wb.createSheet(); + wb.setSheetName(0, "Sheet1"); + HSSFRow row = sheet.createRow(0); + HSSFCell cell = row.createCell((short)0); + + cell.setCellFormula("1=1"); + + HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(sheet, wb); + fe.setCurrentRow(row); + try { + fe.evaluateInCell(cell); + } catch (NumberFormatException e) { + fail("Identified bug 44508"); + } + assertEquals(true, cell.getBooleanCellValue()); + } +} diff --git a/src/testcases/org/apache/poi/AllPOITests.java b/src/testcases/org/apache/poi/AllPOITests.java new file mode 100755 index 000000000..191b71950 --- /dev/null +++ b/src/testcases/org/apache/poi/AllPOITests.java @@ -0,0 +1,44 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi; + +import org.apache.poi.ddf.AllPOIDDFTests; +import org.apache.poi.hpsf.basic.AllPOIHPSFBasicTests; +import org.apache.poi.hssf.HSSFTests; +import org.apache.poi.poifs.AllPOIFSTests; +import org.apache.poi.util.AllPOIUtilTests; + +import junit.framework.Test; +import junit.framework.TestSuite; +/** + * Root Test Suite for entire POI project. (Includes all sub-packages of org.apache.poi)
    + * + * @author Josh Micich + */ +public final class AllPOITests { + public static Test suite() { + TestSuite result = new TestSuite("Tests for org.apache.poi"); + result.addTestSuite(TestPOIDocumentMain.class); + result.addTest(AllPOIDDFTests.suite()); + result.addTest(AllPOIHPSFBasicTests.suite()); + result.addTest(HSSFTests.suite()); + result.addTest(AllPOIFSTests.suite()); + result.addTest(AllPOIUtilTests.suite()); + return result; + } +} diff --git a/src/testcases/org/apache/poi/TestPOIDocumentMain.java b/src/testcases/org/apache/poi/TestPOIDocumentMain.java index be3d9b0a7..5d4d7df19 100644 --- a/src/testcases/org/apache/poi/TestPOIDocumentMain.java +++ b/src/testcases/org/apache/poi/TestPOIDocumentMain.java @@ -85,7 +85,8 @@ public class TestPOIDocumentMain extends TestCase { public void testWriteProperties() throws Exception { // Just check we can write them back out into a filesystem POIFSFileSystem outFS = new POIFSFileSystem(); - doc.writeProperties(outFS); + doc.readProperties(); + doc.writeProperties(outFS); // Should now hold them assertNotNull( @@ -101,7 +102,8 @@ public class TestPOIDocumentMain extends TestCase { // Write them out POIFSFileSystem outFS = new POIFSFileSystem(); - doc.writeProperties(outFS); + doc.readProperties(); + doc.writeProperties(outFS); outFS.writeFilesystem(baos); // Create a new version diff --git a/src/testcases/org/apache/poi/ddf/AllPOIDDFTests.java b/src/testcases/org/apache/poi/ddf/AllPOIDDFTests.java new file mode 100755 index 000000000..9b5fc0bfe --- /dev/null +++ b/src/testcases/org/apache/poi/ddf/AllPOIDDFTests.java @@ -0,0 +1,47 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.ddf; + +import junit.framework.Test; +import junit.framework.TestSuite; +/** + * Tests for org.apache.poi.ddf
    + * + * @author Josh Micich + */ +public final class AllPOIDDFTests { + public static Test suite() { + TestSuite result = new TestSuite("Tests for org.apache.poi.ddf"); + result.addTestSuite(TestEscherBlipWMFRecord.class); + result.addTestSuite(TestEscherBoolProperty.class); + result.addTestSuite(TestEscherBSERecord.class); + result.addTestSuite(TestEscherChildAnchorRecord.class); + result.addTestSuite(TestEscherClientAnchorRecord.class); + result.addTestSuite(TestEscherClientDataRecord.class); + result.addTestSuite(TestEscherContainerRecord.class); + result.addTestSuite(TestEscherDggRecord.class); + result.addTestSuite(TestEscherDgRecord.class); + result.addTestSuite(TestEscherOptRecord.class); + result.addTestSuite(TestEscherPropertyFactory.class); + result.addTestSuite(TestEscherSpgrRecord.class); + result.addTestSuite(TestEscherSplitMenuColorsRecord.class); + result.addTestSuite(TestEscherSpRecord.class); + result.addTestSuite(TestUnknownEscherRecord.class); + return result; + } +} diff --git a/src/testcases/org/apache/poi/hpsf/basic/AllPOIHPSFBasicTests.java b/src/testcases/org/apache/poi/hpsf/basic/AllPOIHPSFBasicTests.java new file mode 100755 index 000000000..5954c9ca5 --- /dev/null +++ b/src/testcases/org/apache/poi/hpsf/basic/AllPOIHPSFBasicTests.java @@ -0,0 +1,39 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hpsf.basic; + +import junit.framework.Test; +import junit.framework.TestSuite; +/** + * Test suite for org.apache.poi.hpsf.basic + * + * @author Josh Micich + */ +public final class AllPOIHPSFBasicTests { + public static Test suite() { + TestSuite result = new TestSuite("Tests for org.apache.poi.hpsf.basic"); + result.addTestSuite(TestBasic.class); + result.addTestSuite(TestClassID.class); + result.addTestSuite(TestEmptyProperties.class); + result.addTestSuite(TestMetaDataIPI.class); + result.addTestSuite(TestUnicode.class); + result.addTestSuite(TestWrite.class); + result.addTestSuite(TestWriteWellKnown.class); + return result; + } +} diff --git a/src/testcases/org/apache/poi/hssf/HSSFTests.java b/src/testcases/org/apache/poi/hssf/HSSFTests.java index 1e0edd682..5b597a67c 100644 --- a/src/testcases/org/apache/poi/hssf/HSSFTests.java +++ b/src/testcases/org/apache/poi/hssf/HSSFTests.java @@ -1,4 +1,3 @@ - /* ==================================================================== Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with @@ -15,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. ==================================================================== */ - + package org.apache.poi.hssf; import junit.framework.Test; @@ -26,80 +25,8 @@ import org.apache.poi.hssf.eventmodel.TestModelFactory; import org.apache.poi.hssf.model.TestDrawingManager; import org.apache.poi.hssf.model.TestFormulaParser; import org.apache.poi.hssf.model.TestSheet; -import org.apache.poi.hssf.record.TestAreaFormatRecord; -import org.apache.poi.hssf.record.TestAreaRecord; -import org.apache.poi.hssf.record.TestAxisLineFormatRecord; -import org.apache.poi.hssf.record.TestAxisOptionsRecord; -import org.apache.poi.hssf.record.TestAxisParentRecord; -import org.apache.poi.hssf.record.TestAxisRecord; -import org.apache.poi.hssf.record.TestAxisUsedRecord; -import org.apache.poi.hssf.record.TestBarRecord; -import org.apache.poi.hssf.record.TestBoundSheetRecord; -import org.apache.poi.hssf.record.TestCategorySeriesAxisRecord; -import org.apache.poi.hssf.record.TestChartRecord; -import org.apache.poi.hssf.record.TestDatRecord; -import org.apache.poi.hssf.record.TestDataFormatRecord; -import org.apache.poi.hssf.record.TestDefaultDataLabelTextPropertiesRecord; -import org.apache.poi.hssf.record.TestFontBasisRecord; -import org.apache.poi.hssf.record.TestFontIndexRecord; -import org.apache.poi.hssf.record.TestFormulaRecord; -import org.apache.poi.hssf.record.TestFrameRecord; -import org.apache.poi.hssf.record.TestLegendRecord; -import org.apache.poi.hssf.record.TestLineFormatRecord; -import org.apache.poi.hssf.record.TestLinkedDataRecord; -import org.apache.poi.hssf.record.TestNameRecord; -import org.apache.poi.hssf.record.TestNumberFormatIndexRecord; -import org.apache.poi.hssf.record.TestObjectLinkRecord; -import org.apache.poi.hssf.record.TestPaletteRecord; -import org.apache.poi.hssf.record.TestPlotAreaRecord; -import org.apache.poi.hssf.record.TestPlotGrowthRecord; -import org.apache.poi.hssf.record.TestRecordFactory; -import org.apache.poi.hssf.record.TestSCLRecord; -import org.apache.poi.hssf.record.TestSSTDeserializer; -import org.apache.poi.hssf.record.TestSSTRecord; -import org.apache.poi.hssf.record.TestSSTRecordSizeCalculator; -import org.apache.poi.hssf.record.TestSeriesChartGroupIndexRecord; -import org.apache.poi.hssf.record.TestSeriesIndexRecord; -import org.apache.poi.hssf.record.TestSeriesLabelsRecord; -import org.apache.poi.hssf.record.TestSeriesListRecord; -import org.apache.poi.hssf.record.TestSeriesRecord; -import org.apache.poi.hssf.record.TestSeriesTextRecord; -import org.apache.poi.hssf.record.TestSeriesToChartGroupRecord; -import org.apache.poi.hssf.record.TestSheetPropertiesRecord; -import org.apache.poi.hssf.record.TestStringRecord; -import org.apache.poi.hssf.record.TestSupBookRecord; -import org.apache.poi.hssf.record.TestTextRecord; -import org.apache.poi.hssf.record.TestTickRecord; -import org.apache.poi.hssf.record.TestUnicodeString; -import org.apache.poi.hssf.record.TestUnitsRecord; -import org.apache.poi.hssf.record.TestValueRangeRecord; -import org.apache.poi.hssf.record.aggregates.TestRowRecordsAggregate; -import org.apache.poi.hssf.record.aggregates.TestValueRecordsAggregate; -import org.apache.poi.hssf.record.formula.AllFormulaTests; -import org.apache.poi.hssf.usermodel.TestBugs; -import org.apache.poi.hssf.usermodel.TestCellStyle; -import org.apache.poi.hssf.usermodel.TestCloneSheet; -import org.apache.poi.hssf.usermodel.TestEscherGraphics; -import org.apache.poi.hssf.usermodel.TestEscherGraphics2d; -import org.apache.poi.hssf.usermodel.TestFontDetails; -import org.apache.poi.hssf.usermodel.TestFormulas; -import org.apache.poi.hssf.usermodel.TestHSSFCell; -import org.apache.poi.hssf.usermodel.TestHSSFClientAnchor; -import org.apache.poi.hssf.usermodel.TestHSSFComment; -import org.apache.poi.hssf.usermodel.TestHSSFDateUtil; -import org.apache.poi.hssf.usermodel.TestHSSFHeaderFooter; -import org.apache.poi.hssf.usermodel.TestHSSFPalette; -import org.apache.poi.hssf.usermodel.TestHSSFRichTextString; -import org.apache.poi.hssf.usermodel.TestHSSFRow; -import org.apache.poi.hssf.usermodel.TestHSSFSheet; -import org.apache.poi.hssf.usermodel.TestHSSFSheetOrder; -import org.apache.poi.hssf.usermodel.TestHSSFSheetSetOrder; -import org.apache.poi.hssf.usermodel.TestHSSFWorkbook; -import org.apache.poi.hssf.usermodel.TestNamedRange; -import org.apache.poi.hssf.usermodel.TestReadWriteChart; -import org.apache.poi.hssf.usermodel.TestSanityChecker; -import org.apache.poi.hssf.usermodel.TestSheetShiftRows; -import org.apache.poi.hssf.usermodel.TestWorkbook; +import org.apache.poi.hssf.record.AllRecordTests; +import org.apache.poi.hssf.usermodel.AllUserModelTests; import org.apache.poi.hssf.util.TestAreaReference; import org.apache.poi.hssf.util.TestCellReference; import org.apache.poi.hssf.util.TestRKUtil; @@ -107,118 +34,36 @@ import org.apache.poi.hssf.util.TestRangeAddress; import org.apache.poi.hssf.util.TestSheetReferences; /** - * Test Suite for running just HSSF tests. Mostly - * this is for my convienience. + * Test Suite for all sub-packages of org.apache.poi.hssf
    + * + * Mostly this is for my convenience. * * @author Andrew C. Oliver acoliver@apache.org */ -public class HSSFTests -{ +public final class HSSFTests { - public static void main(String[] args) - { + public static void main(String[] args) { junit.textui.TestRunner.run(suite()); } - public static Test suite() - { - TestSuite suite = - new TestSuite("Test for org.apache.poi.hssf.usermodel"); - //$JUnit-BEGIN$ - - suite.addTest(new TestSuite(TestBugs.class)); - suite.addTest(new TestSuite(TestCloneSheet.class)); - suite.addTest(new TestSuite(TestEscherGraphics.class)); - suite.addTest(new TestSuite(TestEscherGraphics2d.class)); - suite.addTest(new TestSuite(TestFontDetails.class)); - suite.addTest(new TestSuite(TestHSSFClientAnchor.class)); - suite.addTest(new TestSuite(TestHSSFHeaderFooter.class)); - suite.addTest(new TestSuite(TestHSSFRichTextString.class)); - suite.addTest(new TestSuite(TestHSSFSheetOrder.class)); - suite.addTest(new TestSuite(TestHSSFSheetSetOrder.class)); - suite.addTest(new TestSuite(TestHSSFWorkbook.class)); - suite.addTest(new TestSuite(TestSanityChecker.class)); - suite.addTest(new TestSuite(TestSheetShiftRows.class)); - - suite.addTest(new TestSuite(TestCellStyle.class)); - suite.addTest(new TestSuite(TestFormulas.class)); - suite.addTest(new TestSuite(TestHSSFCell.class)); - suite.addTest(new TestSuite(TestHSSFDateUtil.class)); - suite.addTest(new TestSuite(TestHSSFPalette.class)); - suite.addTest(new TestSuite(TestHSSFRow.class)); - suite.addTest(new TestSuite(TestHSSFSheet.class)); - suite.addTest(new TestSuite(TestNamedRange.class)); - suite.addTest(new TestSuite(TestReadWriteChart.class)); - suite.addTest(new TestSuite(TestWorkbook.class)); - + public static Test suite() { + TestSuite suite = new TestSuite("Tests for org.apache.poi.hssf"); + // $JUnit-BEGIN$ + suite.addTest(AllUserModelTests.suite()); + suite.addTest(AllRecordTests.suite()); suite.addTest(new TestSuite(TestFormulaParser.class)); - suite.addTest(new TestSuite(TestAreaFormatRecord.class)); - suite.addTest(new TestSuite(TestAreaRecord.class)); - suite.addTest(new TestSuite(TestAxisLineFormatRecord.class)); - suite.addTest(new TestSuite(TestAxisOptionsRecord.class)); - suite.addTest(new TestSuite(TestAxisParentRecord.class)); - suite.addTest(new TestSuite(TestAxisRecord.class)); - suite.addTest(new TestSuite(TestAxisUsedRecord.class)); - suite.addTest(new TestSuite(TestBarRecord.class)); - suite.addTest(new TestSuite(TestBoundSheetRecord.class)); - suite.addTest(new TestSuite(TestCategorySeriesAxisRecord.class)); - suite.addTest(new TestSuite(TestChartRecord.class)); - suite.addTest(new TestSuite(TestDatRecord.class)); - suite.addTest(new TestSuite(TestDataFormatRecord.class)); - suite.addTest( - new TestSuite(TestDefaultDataLabelTextPropertiesRecord.class)); - suite.addTest(new TestSuite(TestFontBasisRecord.class)); - suite.addTest(new TestSuite(TestFontIndexRecord.class)); - suite.addTest(new TestSuite(TestFormulaRecord.class)); - suite.addTest(new TestSuite(TestFrameRecord.class)); - suite.addTest(new TestSuite(TestLegendRecord.class)); - suite.addTest(new TestSuite(TestLineFormatRecord.class)); - suite.addTest(new TestSuite(TestLinkedDataRecord.class)); - suite.addTest(new TestSuite(TestNumberFormatIndexRecord.class)); - suite.addTest(new TestSuite(TestObjectLinkRecord.class)); - suite.addTest(new TestSuite(TestPaletteRecord.class)); - suite.addTest(new TestSuite(TestPlotAreaRecord.class)); - suite.addTest(new TestSuite(TestPlotGrowthRecord.class)); - suite.addTest(new TestSuite(TestRecordFactory.class)); - suite.addTest(new TestSuite(TestSCLRecord.class)); - suite.addTest(new TestSuite(TestSSTDeserializer.class)); - suite.addTest(new TestSuite(TestSSTRecord.class)); - suite.addTest(new TestSuite(TestSSTRecordSizeCalculator.class)); - suite.addTest(new TestSuite(TestSeriesChartGroupIndexRecord.class)); - suite.addTest(new TestSuite(TestSeriesIndexRecord.class)); - suite.addTest(new TestSuite(TestSeriesLabelsRecord.class)); - suite.addTest(new TestSuite(TestSeriesListRecord.class)); - suite.addTest(new TestSuite(TestSeriesRecord.class)); - suite.addTest(new TestSuite(TestSeriesTextRecord.class)); - suite.addTest(new TestSuite(TestSeriesToChartGroupRecord.class)); - suite.addTest(new TestSuite(TestSheetPropertiesRecord.class)); - suite.addTest(new TestSuite(TestStringRecord.class)); - suite.addTest(new TestSuite(TestSupBookRecord.class)); - suite.addTest(new TestSuite(TestTextRecord.class)); - suite.addTest(new TestSuite(TestTickRecord.class)); - suite.addTest(new TestSuite(TestUnicodeString.class)); - suite.addTest(new TestSuite(TestUnitsRecord.class)); - suite.addTest(new TestSuite(TestValueRangeRecord.class)); - suite.addTest(new TestSuite(TestRowRecordsAggregate.class)); suite.addTest(new TestSuite(TestAreaReference.class)); suite.addTest(new TestSuite(TestCellReference.class)); - suite.addTest(new TestSuite(TestRangeAddress.class)); + suite.addTest(new TestSuite(TestRangeAddress.class)); suite.addTest(new TestSuite(TestRKUtil.class)); suite.addTest(new TestSuite(TestSheetReferences.class)); - - - suite.addTest(AllFormulaTests.suite()); - suite.addTest(new TestSuite(TestValueRecordsAggregate.class)); - suite.addTest(new TestSuite(TestNameRecord.class)); - suite.addTest(new TestSuite(TestEventRecordFactory.class)); - suite.addTest(new TestSuite(TestModelFactory.class)); - suite.addTest(new TestSuite(TestDrawingManager.class)); - suite.addTest(new TestSuite(TestSheet.class)); - - suite.addTest(new TestSuite(TestHSSFComment.class)); - //$JUnit-END$ + suite.addTest(new TestSuite(TestEventRecordFactory.class)); + suite.addTest(new TestSuite(TestModelFactory.class)); + suite.addTest(new TestSuite(TestDrawingManager.class)); + suite.addTest(new TestSuite(TestSheet.class)); + // $JUnit-END$ return suite; } } diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/data/44297.xls b/src/testcases/org/apache/poi/hssf/data/44297.xls similarity index 100% rename from src/scratchpad/testcases/org/apache/poi/hssf/data/44297.xls rename to src/testcases/org/apache/poi/hssf/data/44297.xls diff --git a/src/testcases/org/apache/poi/hssf/data/AbnormalSharedFormulaFlag.xls b/src/testcases/org/apache/poi/hssf/data/AbnormalSharedFormulaFlag.xls new file mode 100755 index 0000000000000000000000000000000000000000..788865b3b9788aea28563247e89fc0ade35abd0e GIT binary patch literal 17920 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 literal 0 HcmV?d00001 diff --git a/src/testcases/org/apache/poi/hssf/data/FormulaEvalTestData.xls b/src/testcases/org/apache/poi/hssf/data/FormulaEvalTestData.xls new file mode 100644 index 0000000000000000000000000000000000000000..6260d878bc89ada3ebe92c7fa615baa1c387cde3 GIT binary patch literal 136704 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ð%D8% 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 literal 0 HcmV?d00001 diff --git a/src/testcases/org/apache/poi/hssf/data/LookupFunctionsTestCaseData.xls b/src/testcases/org/apache/poi/hssf/data/LookupFunctionsTestCaseData.xls new file mode 100755 index 0000000000000000000000000000000000000000..f4b35fb93504540b85455ff37341318cbcd74e1e GIT binary patch literal 39936 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 literal 0 HcmV?d00001 diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/data/MissingBits.xls b/src/testcases/org/apache/poi/hssf/data/MissingBits.xls similarity index 100% rename from src/scratchpad/testcases/org/apache/poi/hssf/data/MissingBits.xls rename to src/testcases/org/apache/poi/hssf/data/MissingBits.xls diff --git a/src/testcases/org/apache/poi/hssf/data/OddStyleRecord.xls b/src/testcases/org/apache/poi/hssf/data/OddStyleRecord.xls new file mode 100644 index 0000000000000000000000000000000000000000..dcaa79b5e1077145000128498fff0fba30dc08d8 GIT binary patch literal 17408 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 literal 0 HcmV?d00001 diff --git a/src/testcases/org/apache/poi/hssf/data/ReadOnlyRecommended.xls b/src/testcases/org/apache/poi/hssf/data/ReadOnlyRecommended.xls new file mode 100644 index 0000000000000000000000000000000000000000..d479b94f60801ebc9fcaab0c40490a9f50dd77b2 GIT binary patch literal 13824 zcmeHOUuc_E6#pen+H~#OwB6>MEQS=RYtz|nii6oTiWPkrmJZoql{QT~T3T00t4vTA zb$gpI*@G`b88T3W{qfIy5ozD{G8AF>?y;!#}&uR^@h#<~}7AS!)$wPP?tAWAE%tSgn7Vm#DePA|zgJ~aUp5_~H zpGA`nD4KbydIM?+a(KknCDeuptSg0Kl#40V8jACYYp%Xp8u$*_sEt27G zFP73_u{F`1VB2taYPdTVN@5c_)UjkXR92V1!oC+t@Y0k!VgO5gd76^DzP^&fJJE$Z z${hN=i~jwt=C;%?k2RVO_^A-L(i8J>>2<+P|D)gZ`+Cj5>)BV+Yt-x6uVj4yah+a~ zd_2269DZJ^-&94PtfKF%qObpr{&+BiXs)K9vL#n*Wos^EWm~S!O5|YWnq0UrV$Xyl zeA1i=E6Q5zo){mW+ak@^_VrlQY?bEQx-y$>M-k#&N@5`pA@iR5Ab=~h()<$hVVfS! z6tIf7!?>xQ5=k!7brrx?^J@@jjeHByeD+3GF! zV2je(l{dcsc-QDa->~UBP2XkmdDG-*761~DJ_s>D*#UNQ7vXYI#V27!a5`PN}r+A50I1FWw zxvE3JA>a^j2si}(mk3c6vCNtTY*_r)w z@&1FE@o9@8clxdLJBKa=QtXd`^UtWR<)C~yatJsC90Cpjhk!%CA>a^j2si{B0uBL( zfI~pR{sAfxt^^2e}JqXX!eqCB9D<9$kSxy_H$(A z{ZEtEkq?u#A8?ec{egGM<@*8JOAsk{Z|-QYv2?64MN7*Voyb=E5#DcdrRvsFbsHLK zC`Z~{&>(wevQxAA5pUM|YYayY0f&G?z#-rea0oaA90Cpjhk!%CA>a^L$p|QqS8A*r zSoybJan+lX%Bl6g4(0lKgHma;a(3nKdVy1UzuH?^>NkJo`Tnb+%JsD$p!Ta|m5% z{`oI6-^`iYp7ZrrCF93FTyPna+-l_EMye3`IdqHZ`EEV{mZwsZb>niTNiyyZLZHyk zj?DMr=fxKaz8qyT54a2uKJcDp2>eJN!d}b*I(qjE3=PEM!`<=du>B9awOWU6OX@Jn zf!D^Ag`=d-<`@*}91Tk~d|&y_XxPRQac9<5)U4E$g>9a8<$F=X3Jqs7wD76QTvAFC z8t^M6QcoY2p$=RswzgmZdk{ekHXdO-!ng}j(z-E(CM>}D2@*c51H+W*#vn_}wbTz+ zpV!)~axs?OuC1WA96po+6~`+o>nbZ7TSN73GU#2zCza2iOK9ER73tlP%Me6?JiIPL zLu0Nyb3tz<{0L6un50G7mZKJpKv;~|+FFxvy;GNLHeFg#U%|3)eS5e*=&eHymWp7O zEtHcM`UL(q)PWtCYlsz?WXqvs&8=CJHQ0h>xF^S;E1kCQimKUMxdnI=WgPZ*JB_Pp z4OOSpp_-s~G2UXY=af1i<+9j#8w)cmVkbujv!Z4R-r-Pk)i^v8HKiF_a6(%_11@ki ztzpfDsx;;z-4LC`_5w6aasJRT*vB?-Eo=7`j~9VNUP|JUM83vz5?+l< z69pP00gdM-e5seYN^zo~3-$Wt2cNnL@I;k2=OM zM*cqjrAS8~^JAirCgo3(s!uhj4zWTZl^dgcDwRZ%`Baj!)SDVhqPZ`H=CLu-$t^xf z-$(U*NgO$H1Y|&Sl7wdd(k2B{PdjBzpw~zk4lRM&R(n9+U!Pqq(3rb@9iFrMSAU#fq1{&xoN{Zr?8`E zI2w!D@sX(A6CK>QXDC+T6s+Ih6WJRdhz{LUI)xcrMXnS8MKsjoFQb>LIgXR6I?oJ7 z&dwt+VdpW^CfkjHgPIHG`7>lv1u_0FX(R+`!51f9h+fX# zae7()57G190I$*W+{#lGbaMZRLOf_H&(;;o8(UhMT9{RutaTGE8gl3x2V_$c?Tg#8 z2^o~-1laJ+XHV=pZ)rNTs`9(&c}vzD;|u}@0fT@+z#w1{FbEg~3<3rLgMdN6AYc&4 zB5Vo^JnNvBBvv literal 0 HcmV?d00001 diff --git a/src/testcases/org/apache/poi/hssf/data/TestDataValidation.xls b/src/testcases/org/apache/poi/hssf/data/TestDataValidation.xls deleted file mode 100644 index 0b2a86948605b08693921925b3d5d6fe708872c6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21504 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 diff --git a/src/testcases/org/apache/poi/hssf/data/externalFunctionExample.xls b/src/testcases/org/apache/poi/hssf/data/externalFunctionExample.xls new file mode 100755 index 0000000000000000000000000000000000000000..07eafb414416e98875b4c0a9cd2a78b453a5413a GIT binary patch literal 16384 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 literal 0 HcmV?d00001 diff --git a/src/testcases/org/apache/poi/hssf/data/logoKarmokar4.png b/src/testcases/org/apache/poi/hssf/data/logoKarmokar4.png new file mode 100755 index 0000000000000000000000000000000000000000..90a915a3a993732fb52cf87c32cf3ab2c64e62b7 GIT binary patch literal 15792 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 literal 0 HcmV?d00001 diff --git a/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java b/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java index 1b41d87d4..f922e75d8 100644 --- a/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java +++ b/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java @@ -1,4 +1,3 @@ - /* ==================================================================== Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with @@ -18,24 +17,35 @@ package org.apache.poi.hssf.model; +import junit.framework.AssertionFailedError; import junit.framework.TestCase; +import org.apache.poi.hssf.model.FormulaParser.FormulaParseException; import org.apache.poi.hssf.record.formula.AbstractFunctionPtg; import org.apache.poi.hssf.record.formula.AddPtg; +import org.apache.poi.hssf.record.formula.AreaPtg; import org.apache.poi.hssf.record.formula.AttrPtg; import org.apache.poi.hssf.record.formula.BoolPtg; +import org.apache.poi.hssf.record.formula.ConcatPtg; import org.apache.poi.hssf.record.formula.DividePtg; import org.apache.poi.hssf.record.formula.EqualPtg; +import org.apache.poi.hssf.record.formula.ErrPtg; +import org.apache.poi.hssf.record.formula.FuncPtg; import org.apache.poi.hssf.record.formula.FuncVarPtg; import org.apache.poi.hssf.record.formula.IntPtg; import org.apache.poi.hssf.record.formula.LessEqualPtg; import org.apache.poi.hssf.record.formula.LessThanPtg; +import org.apache.poi.hssf.record.formula.MissingArgPtg; +import org.apache.poi.hssf.record.formula.MultiplyPtg; import org.apache.poi.hssf.record.formula.NamePtg; import org.apache.poi.hssf.record.formula.NotEqualPtg; import org.apache.poi.hssf.record.formula.NumberPtg; +import org.apache.poi.hssf.record.formula.PercentPtg; +import org.apache.poi.hssf.record.formula.PowerPtg; import org.apache.poi.hssf.record.formula.Ptg; import org.apache.poi.hssf.record.formula.ReferencePtg; import org.apache.poi.hssf.record.formula.StringPtg; +import org.apache.poi.hssf.record.formula.SubtractPtg; import org.apache.poi.hssf.record.formula.UnaryMinusPtg; import org.apache.poi.hssf.record.formula.UnaryPlusPtg; import org.apache.poi.hssf.usermodel.HSSFCell; @@ -49,27 +59,27 @@ import org.apache.poi.hssf.usermodel.HSSFWorkbook; * Some tests are also done in scratchpad, if they need * HSSFFormulaEvaluator, which is there */ -public class TestFormulaParser extends TestCase { +public final class TestFormulaParser extends TestCase { - public TestFormulaParser(String name) { - super(name); - } - public void setUp(){ - - } - - public void tearDown() { - + /** + * @return parsed token array already confirmed not null + */ + private static Ptg[] parseFormula(String s) { + FormulaParser fp = new FormulaParser(s, null); + fp.parse(); + Ptg[] result = fp.getRPNPtg(); + assertNotNull("Ptg array should not be null", result); + return result; } public void testSimpleFormula() { - FormulaParser fp = new FormulaParser("2+2;",null); + FormulaParser fp = new FormulaParser("2+2",null); fp.parse(); Ptg[] ptgs = fp.getRPNPtg(); assertTrue("three tokens expected, got "+ptgs.length,ptgs.length == 3); } public void testFormulaWithSpace1() { - FormulaParser fp = new FormulaParser(" 2 + 2 ;",null); + FormulaParser fp = new FormulaParser(" 2 + 2 ",null); fp.parse(); Ptg[] ptgs = fp.getRPNPtg(); assertTrue("three tokens expected, got "+ptgs.length,ptgs.length == 3); @@ -82,7 +92,7 @@ public class TestFormulaParser extends TestCase { public void testFormulaWithSpace2() { Ptg[] ptgs; FormulaParser fp; - fp = new FormulaParser("2+ sum( 3 , 4) ;",null); + fp = new FormulaParser("2+ sum( 3 , 4) ",null); fp.parse(); ptgs = fp.getRPNPtg(); assertTrue("five tokens expected, got "+ptgs.length,ptgs.length == 5); @@ -91,7 +101,7 @@ public class TestFormulaParser extends TestCase { public void testFormulaWithSpaceNRef() { Ptg[] ptgs; FormulaParser fp; - fp = new FormulaParser("sum( A2:A3 );",null); + fp = new FormulaParser("sum( A2:A3 )",null); fp.parse(); ptgs = fp.getRPNPtg(); assertTrue("two tokens expected, got "+ptgs.length,ptgs.length == 2); @@ -100,7 +110,7 @@ public class TestFormulaParser extends TestCase { public void testFormulaWithString() { Ptg[] ptgs; FormulaParser fp; - fp = new FormulaParser("\"hello\" & \"world\" ;",null); + fp = new FormulaParser("\"hello\" & \"world\" ",null); fp.parse(); ptgs = fp.getRPNPtg(); assertTrue("three token expected, got " + ptgs.length, ptgs.length == 3); @@ -273,20 +283,21 @@ public class TestFormulaParser extends TestCase { } public void testMacroFunction() { - Workbook w = new Workbook(); + Workbook w = Workbook.createWorkbook(); FormulaParser fp = new FormulaParser("FOO()", w); fp.parse(); Ptg[] ptg = fp.getRPNPtg(); - AbstractFunctionPtg tfunc = (AbstractFunctionPtg) ptg[0]; - assertEquals("externalflag", tfunc.getName()); - - NamePtg tname = (NamePtg) ptg[1]; + // the name gets encoded as the first arg + NamePtg tname = (NamePtg) ptg[0]; assertEquals("FOO", tname.toFormulaString(w)); + + AbstractFunctionPtg tfunc = (AbstractFunctionPtg) ptg[1]; + assertEquals("externalflag", tfunc.getName()); } public void testEmbeddedSlash() { - FormulaParser fp = new FormulaParser("HYPERLINK(\"http://www.jakarta.org\",\"Jakarta\");",null); + FormulaParser fp = new FormulaParser("HYPERLINK(\"http://www.jakarta.org\",\"Jakarta\")",null); fp.parse(); Ptg[] ptg = fp.getRPNPtg(); assertTrue("first ptg is string",ptg[0] instanceof StringPtg); @@ -397,7 +408,7 @@ public class TestFormulaParser extends TestCase { public void testUnderscore() { HSSFWorkbook wb = new HSSFWorkbook(); - wb.createSheet("Cash_Flow");; + wb.createSheet("Cash_Flow"); HSSFSheet sheet = wb.createSheet("Test"); HSSFRow row = sheet.createRow(0); @@ -438,7 +449,7 @@ public class TestFormulaParser extends TestCase { public void testExponentialInSheet() throws Exception { HSSFWorkbook wb = new HSSFWorkbook(); - wb.createSheet("Cash_Flow");; + wb.createSheet("Cash_Flow"); HSSFSheet sheet = wb.createSheet("Test"); HSSFRow row = sheet.createRow(0); @@ -514,7 +525,7 @@ public class TestFormulaParser extends TestCase { public void testNumbers() { HSSFWorkbook wb = new HSSFWorkbook(); - wb.createSheet("Cash_Flow");; + wb.createSheet("Cash_Flow"); HSSFSheet sheet = wb.createSheet("Test"); HSSFRow row = sheet.createRow(0); @@ -553,7 +564,7 @@ public class TestFormulaParser extends TestCase { public void testRanges() { HSSFWorkbook wb = new HSSFWorkbook(); - wb.createSheet("Cash_Flow");; + wb.createSheet("Cash_Flow"); HSSFSheet sheet = wb.createSheet("Test"); HSSFRow row = sheet.createRow(0); @@ -571,5 +582,266 @@ public class TestFormulaParser extends TestCase { cell.setCellFormula("A1...A2"); formula = cell.getCellFormula(); assertEquals("A1:A2", formula); - } + } + + /** + * Test for bug observable at svn revision 618865 (5-Feb-2008)
    + * a formula consisting of a single no-arg function got rendered without the function braces + */ + public void testToFormulaStringZeroArgFunction() { + + Workbook book = Workbook.createWorkbook(); // not really used in this test + + Ptg[] ptgs = { + new FuncPtg(10, 0), + }; + assertEquals("NA()", FormulaParser.toFormulaString(book, ptgs)); + } + + public void testPercent() { + Ptg[] ptgs; + ptgs = parseFormula("5%"); + assertEquals(2, ptgs.length); + assertEquals(ptgs[0].getClass(), IntPtg.class); + assertEquals(ptgs[1].getClass(), PercentPtg.class); + + // spaces OK + ptgs = parseFormula(" 250 % "); + assertEquals(2, ptgs.length); + assertEquals(ptgs[0].getClass(), IntPtg.class); + assertEquals(ptgs[1].getClass(), PercentPtg.class); + + + // double percent OK + ptgs = parseFormula("12345.678%%"); + assertEquals(3, ptgs.length); + assertEquals(ptgs[0].getClass(), NumberPtg.class); + assertEquals(ptgs[1].getClass(), PercentPtg.class); + assertEquals(ptgs[2].getClass(), PercentPtg.class); + + // percent of a bracketed expression + ptgs = parseFormula("(A1+35)%*B1%"); + assertEquals(8, ptgs.length); + assertEquals(ptgs[4].getClass(), PercentPtg.class); + assertEquals(ptgs[6].getClass(), PercentPtg.class); + + // percent of a text quantity + ptgs = parseFormula("\"8.75\"%"); + assertEquals(2, ptgs.length); + assertEquals(ptgs[0].getClass(), StringPtg.class); + assertEquals(ptgs[1].getClass(), PercentPtg.class); + + // percent to the power of + ptgs = parseFormula("50%^3"); + assertEquals(4, ptgs.length); + assertEquals(ptgs[0].getClass(), IntPtg.class); + assertEquals(ptgs[1].getClass(), PercentPtg.class); + assertEquals(ptgs[2].getClass(), IntPtg.class); + assertEquals(ptgs[3].getClass(), PowerPtg.class); + + // + // things that parse OK but would *evaluate* to an error + + ptgs = parseFormula("\"abc\"%"); + assertEquals(2, ptgs.length); + assertEquals(ptgs[0].getClass(), StringPtg.class); + assertEquals(ptgs[1].getClass(), PercentPtg.class); + + ptgs = parseFormula("#N/A%"); + assertEquals(2, ptgs.length); + assertEquals(ptgs[0].getClass(), ErrPtg.class); + assertEquals(ptgs[1].getClass(), PercentPtg.class); + } + + /** + * Tests combinations of various operators in the absence of brackets + */ + public void testPrecedenceAndAssociativity() { + + Class[] expClss; + + // TRUE=TRUE=2=2 evaluates to FALSE + expClss = new Class[] { BoolPtg.class, BoolPtg.class, EqualPtg.class, + IntPtg.class, EqualPtg.class, IntPtg.class, EqualPtg.class, }; + confirmTokenClasses("TRUE=TRUE=2=2", expClss); + + + // 2^3^2 evaluates to 64 not 512 + expClss = new Class[] { IntPtg.class, IntPtg.class, PowerPtg.class, + IntPtg.class, PowerPtg.class, }; + confirmTokenClasses("2^3^2", expClss); + + // "abc" & 2 + 3 & "def" evaluates to "abc5def" + expClss = new Class[] { StringPtg.class, IntPtg.class, IntPtg.class, + AddPtg.class, ConcatPtg.class, StringPtg.class, ConcatPtg.class, }; + confirmTokenClasses("\"abc\"&2+3&\"def\"", expClss); + + + // (1 / 2) - (3 * 4) + expClss = new Class[] { IntPtg.class, IntPtg.class, DividePtg.class, + IntPtg.class, IntPtg.class, MultiplyPtg.class, SubtractPtg.class, }; + confirmTokenClasses("1/2-3*4", expClss); + + // 2 * (2^2) + expClss = new Class[] { IntPtg.class, IntPtg.class, IntPtg.class, PowerPtg.class, MultiplyPtg.class, }; + // NOT: (2 *2) ^ 2 -> int int multiply int power + confirmTokenClasses("2*2^2", expClss); + + // 2^200% -> 2 not 1.6E58 + expClss = new Class[] { IntPtg.class, IntPtg.class, PercentPtg.class, PowerPtg.class, }; + confirmTokenClasses("2^200%", expClss); + } + + private static void confirmTokenClasses(String formula, Class[] expectedClasses) { + Ptg[] ptgs = parseFormula(formula); + assertEquals(expectedClasses.length, ptgs.length); + for (int i = 0; i < expectedClasses.length; i++) { + if(expectedClasses[i] != ptgs[i].getClass()) { + fail("difference at token[" + i + "]: expected (" + + expectedClasses[i].getName() + ") but got (" + + ptgs[i].getClass().getName() + ")"); + } + } + } + + public void testPower() { + confirmTokenClasses("2^5", new Class[] { IntPtg.class, IntPtg.class, PowerPtg.class, }); + } + + private static Ptg parseSingleToken(String formula, Class ptgClass) { + Ptg[] ptgs = parseFormula(formula); + assertEquals(1, ptgs.length); + Ptg result = ptgs[0]; + assertEquals(ptgClass, result.getClass()); + return result; + } + + public void testParseNumber() { + IntPtg ip; + + // bug 33160 + ip = (IntPtg) parseSingleToken("40", IntPtg.class); + assertEquals(40, ip.getValue()); + ip = (IntPtg) parseSingleToken("40000", IntPtg.class); + assertEquals(40000, ip.getValue()); + + // check the upper edge of the IntPtg range: + ip = (IntPtg) parseSingleToken("65535", IntPtg.class); + assertEquals(65535, ip.getValue()); + NumberPtg np = (NumberPtg) parseSingleToken("65536", NumberPtg.class); + assertEquals(65536, np.getValue(), 0); + + np = (NumberPtg) parseSingleToken("65534.6", NumberPtg.class); + assertEquals(65534.6, np.getValue(), 0); + } + + public void testMissingArgs() { + + Class[] expClss; + + expClss = new Class[] { ReferencePtg.class, MissingArgPtg.class, ReferencePtg.class, + FuncVarPtg.class, }; + confirmTokenClasses("if(A1, ,C1)", expClss); + + expClss = new Class[] { MissingArgPtg.class, AreaPtg.class, MissingArgPtg.class, + FuncVarPtg.class, }; + confirmTokenClasses("counta( , A1:B2, )", expClss); + } + + public void testParseErrorLiterals() { + + confirmParseErrorLiteral(ErrPtg.NULL_INTERSECTION, "#NULL!"); + confirmParseErrorLiteral(ErrPtg.DIV_ZERO, "#DIV/0!"); + confirmParseErrorLiteral(ErrPtg.VALUE_INVALID, "#VALUE!"); + confirmParseErrorLiteral(ErrPtg.REF_INVALID, "#REF!"); + confirmParseErrorLiteral(ErrPtg.NAME_INVALID, "#NAME?"); + confirmParseErrorLiteral(ErrPtg.NUM_ERROR, "#NUM!"); + confirmParseErrorLiteral(ErrPtg.N_A, "#N/A"); + } + + private static void confirmParseErrorLiteral(ErrPtg expectedToken, String formula) { + assertEquals(expectedToken, parseSingleToken(formula, ErrPtg.class)); + } + + /** + * To aid readability the parameters have been encoded with single quotes instead of double + * quotes. This method converts single quotes to double quotes before performing the parse + * and result check. + */ + private static void confirmStringParse(String singleQuotedValue) { + // formula: internal quotes become double double, surround with double quotes + String formula = '"' + singleQuotedValue.replaceAll("'", "\"\"") + '"'; + String expectedValue = singleQuotedValue.replace('\'', '"'); + + StringPtg sp = (StringPtg) parseSingleToken(formula, StringPtg.class); + assertEquals(expectedValue, sp.getValue()); + } + + public void testPaseStringLiterals() { + confirmStringParse("goto considered harmful"); + + confirmStringParse("goto 'considered' harmful"); + + confirmStringParse(""); + confirmStringParse("'"); + confirmStringParse("''"); + confirmStringParse("' '"); + confirmStringParse(" ' "); + } + + public void testParseSumIfSum() { + String formulaString; + Ptg[] ptgs; + ptgs = parseFormula("sum(5, 2, if(3>2, sum(A1:A2), 6))"); + formulaString = FormulaParser.toFormulaString(null, ptgs); + assertEquals("SUM(5,2,IF(3>2,SUM(A1:A2),6))", formulaString); + + ptgs = parseFormula("if(1<2,sum(5, 2, if(3>2, sum(A1:A2), 6)),4)"); + formulaString = FormulaParser.toFormulaString(null, ptgs); + assertEquals("IF(1<2,SUM(5,2,IF(3>2,SUM(A1:A2),6)),4)", formulaString); + } + public void testParserErrors() { + parseExpectedException("1 2"); + parseExpectedException(" 12 . 345 "); + parseExpectedException("1 .23 "); + + parseExpectedException("sum(#NAME)"); + parseExpectedException("1 + #N / A * 2"); + parseExpectedException("#value?"); + parseExpectedException("#DIV/ 0+2"); + + + if (false) { // TODO - add functionality to detect func arg count mismatch + parseExpectedException("IF(TRUE)"); + parseExpectedException("countif(A1:B5, C1, D1)"); + } + } + + private static void parseExpectedException(String formula) { + try { + parseFormula(formula); + throw new AssertionFailedError("expected parse exception"); + } catch (FormulaParseException e) { + // expected during successful test + assertNotNull(e.getMessage()); + } catch (RuntimeException e) { + e.printStackTrace(); + fail("Wrong exception:" + e.getMessage()); + } + } + + public void testSetFormulaWithRowBeyond32768_Bug44539() { + + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sheet = wb.createSheet(); + wb.setSheetName(0, "Sheet1"); + + HSSFRow row = sheet.createRow(0); + HSSFCell cell = row.createCell((short)0); + cell.setCellFormula("SUM(A32769:A32770)"); + if("SUM(A-32767:A-32766)".equals(cell.getCellFormula())) { + fail("Identified bug 44539"); + } + assertEquals("SUM(A32769:A32770)", cell.getCellFormula()); + } } diff --git a/src/testcases/org/apache/poi/hssf/record/AllRecordTests.java b/src/testcases/org/apache/poi/hssf/record/AllRecordTests.java new file mode 100755 index 000000000..b1acfeafa --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/record/AllRecordTests.java @@ -0,0 +1,104 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + + +package org.apache.poi.hssf.record; + +import org.apache.poi.hssf.record.formula.AllFormulaTests; + +import junit.framework.Test; +import junit.framework.TestSuite; + +/** + * Collects all tests for package org.apache.poi.hssf.record. + * + * @author Josh Micich + */ +public class AllRecordTests { + + public static Test suite() { + TestSuite result = new TestSuite("Tests for org.apache.poi.hssf.record"); + + result.addTest(AllFormulaTests.suite()); + + result.addTestSuite(TestAreaFormatRecord.class); + result.addTestSuite(TestAreaRecord.class); + result.addTestSuite(TestAxisLineFormatRecord.class); + result.addTestSuite(TestAxisOptionsRecord.class); + result.addTestSuite(TestAxisParentRecord.class); + result.addTestSuite(TestAxisRecord.class); + result.addTestSuite(TestAxisUsedRecord.class); + result.addTestSuite(TestBOFRecord.class); + result.addTestSuite(TestBarRecord.class); + result.addTestSuite(TestBoundSheetRecord.class); + result.addTestSuite(TestCategorySeriesAxisRecord.class); + result.addTestSuite(TestChartRecord.class); + result.addTestSuite(TestChartTitleFormatRecord.class); + result.addTestSuite(TestCommonObjectDataSubRecord.class); + result.addTestSuite(TestDatRecord.class); + result.addTestSuite(TestDataFormatRecord.class); + result.addTestSuite(TestDefaultDataLabelTextPropertiesRecord.class); + result.addTestSuite(TestDrawingGroupRecord.class); + result.addTestSuite(TestEmbeddedObjectRefSubRecord.class); + result.addTestSuite(TestEndSubRecord.class); + result.addTestSuite(TestEscherAggregate.class); + result.addTestSuite(TestFontBasisRecord.class); + result.addTestSuite(TestFontIndexRecord.class); + result.addTestSuite(TestFormulaRecord.class); + result.addTestSuite(TestFrameRecord.class); + result.addTestSuite(TestHyperlinkRecord.class); + result.addTestSuite(TestLegendRecord.class); + result.addTestSuite(TestLineFormatRecord.class); + result.addTestSuite(TestLinkedDataRecord.class); + result.addTestSuite(TestMergeCellsRecord.class); + result.addTestSuite(TestNameRecord.class); + result.addTestSuite(TestNoteRecord.class); + result.addTestSuite(TestNoteStructureSubRecord.class); + result.addTestSuite(TestNumberFormatIndexRecord.class); + result.addTestSuite(TestObjRecord.class); + result.addTestSuite(TestObjectLinkRecord.class); + result.addTestSuite(TestPaletteRecord.class); + result.addTestSuite(TestPaneRecord.class); + result.addTestSuite(TestPlotAreaRecord.class); + result.addTestSuite(TestPlotGrowthRecord.class); + result.addTestSuite(TestRecordFactory.class); + result.addTestSuite(TestSCLRecord.class); + result.addTestSuite(TestSSTDeserializer.class); + result.addTestSuite(TestSSTRecord.class); + result.addTestSuite(TestSSTRecordSizeCalculator.class); + result.addTestSuite(TestSeriesChartGroupIndexRecord.class); + result.addTestSuite(TestSeriesIndexRecord.class); + result.addTestSuite(TestSeriesLabelsRecord.class); + result.addTestSuite(TestSeriesListRecord.class); + result.addTestSuite(TestSeriesRecord.class); + result.addTestSuite(TestSeriesTextRecord.class); + result.addTestSuite(TestSeriesToChartGroupRecord.class); + result.addTestSuite(TestSheetPropertiesRecord.class); + result.addTestSuite(TestStringRecord.class); + result.addTestSuite(TestSubRecord.class); + result.addTestSuite(TestSupBookRecord.class); + result.addTestSuite(TestTextObjectBaseRecord.class); + result.addTestSuite(TestTextObjectRecord.class); + result.addTestSuite(TestTextRecord.class); + result.addTestSuite(TestTickRecord.class); + result.addTestSuite(TestUnicodeNameRecord.class); + result.addTestSuite(TestUnicodeString.class); + result.addTestSuite(TestUnitsRecord.class); + result.addTestSuite(TestValueRangeRecord.class); + return result; + } +} diff --git a/src/testcases/org/apache/poi/hssf/record/TestDVALRecord.java b/src/testcases/org/apache/poi/hssf/record/TestDVALRecord.java new file mode 100755 index 000000000..bcfb76645 --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/record/TestDVALRecord.java @@ -0,0 +1,55 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hssf.record; + +import java.io.ByteArrayInputStream; + +import org.apache.poi.util.LittleEndian; + +import junit.framework.TestCase; + +/** + * + * @author Josh Micich + */ +public final class TestDVALRecord extends TestCase { + public void testRead() { + + byte[] data = new byte[22]; + LittleEndian.putShort(data, 0, DVALRecord.sid); + LittleEndian.putShort(data, 2, (short)18); + LittleEndian.putShort(data, 4, (short)55); + LittleEndian.putInt(data, 6, 56); + LittleEndian.putInt(data, 10, 57); + LittleEndian.putInt(data, 14, 58); + LittleEndian.putInt(data, 18, 59); + + RecordInputStream in = new RecordInputStream(new ByteArrayInputStream(data)); + in.nextRecord(); + DVALRecord dv = new DVALRecord(in); + + assertEquals(55, dv.getOptions()); + assertEquals(56, dv.getHorizontalPos()); + assertEquals(57, dv.getVerticalPos()); + assertEquals(58, dv.getObjectID()); + if(dv.getDVRecNo() == 0) { + fail("Identified bug 44510"); + } + assertEquals(59, dv.getDVRecNo()); + } +} diff --git a/src/testcases/org/apache/poi/hssf/record/TestSupBookRecord.java b/src/testcases/org/apache/poi/hssf/record/TestSupBookRecord.java index fca5bae54..3727989ef 100644 --- a/src/testcases/org/apache/poi/hssf/record/TestSupBookRecord.java +++ b/src/testcases/org/apache/poi/hssf/record/TestSupBookRecord.java @@ -29,15 +29,25 @@ import junit.framework.TestCase; * * @author Andrew C. Oliver (acoliver at apache dot org) */ -public class TestSupBookRecord - extends TestCase -{ +public final class TestSupBookRecord extends TestCase { /** * This contains a fake data section of a SubBookRecord */ - byte[] data = new byte[] { - (byte)0x04,(byte)0x00,(byte)0x01,(byte)0x04 + byte[] dataIR = new byte[] { + (byte)0x04,(byte)0x00,(byte)0x01,(byte)0x04, }; + byte[] dataAIF = new byte[] { + (byte)0x01,(byte)0x00,(byte)0x01,(byte)0x3A, + }; + byte[] dataER = new byte[] { + (byte)0x02,(byte)0x00, + (byte)0x07,(byte)0x00, (byte)0x00, + (byte)'t', (byte)'e', (byte)'s', (byte)'t', (byte)'U', (byte)'R', (byte)'L', + (byte)0x06,(byte)0x00, (byte)0x00, + (byte)'S', (byte)'h', (byte)'e', (byte)'e', (byte)'t', (byte)'1', + (byte)0x06,(byte)0x00, (byte)0x00, + (byte)'S', (byte)'h', (byte)'e', (byte)'e', (byte)'t', (byte)'2', + }; public TestSupBookRecord(String name) { @@ -47,36 +57,67 @@ public class TestSupBookRecord /** * tests that we can load the record */ - public void testLoad() - throws Exception - { + public void testLoadIR() { - SupBookRecord record = new SupBookRecord(new TestcaseRecordInputStream((short)0x01AE, (short)data.length, data)); - assertEquals( 0x401, record.getFlag()); //expected flag + SupBookRecord record = new SupBookRecord(new TestcaseRecordInputStream((short)0x01AE, dataIR)); + assertTrue( record.isInternalReferences() ); //expected flag assertEquals( 0x4, record.getNumberOfSheets() ); //expected # of sheets assertEquals( 8, record.getRecordSize() ); //sid+size+data record.validateSid((short)0x01AE); } + /** + * tests that we can load the record + */ + public void testLoadER() { + + SupBookRecord record = new SupBookRecord(new TestcaseRecordInputStream((short)0x01AE, dataER)); + assertTrue( record.isExternalReferences() ); //expected flag + assertEquals( 0x2, record.getNumberOfSheets() ); //expected # of sheets + + assertEquals( 34, record.getRecordSize() ); //sid+size+data + + assertEquals("testURL", record.getURL().getString()); + UnicodeString[] sheetNames = record.getSheetNames(); + assertEquals(2, sheetNames.length); + assertEquals("Sheet1", sheetNames[0].getString()); + assertEquals("Sheet2", sheetNames[1].getString()); + + record.validateSid((short)0x01AE); + } - + /** + * tests that we can load the record + */ + public void testLoadAIF() { + + SupBookRecord record = new SupBookRecord(new TestcaseRecordInputStream((short)0x01AE, dataAIF)); + assertTrue( record.isAddInFunctions() ); //expected flag + assertEquals( 0x1, record.getNumberOfSheets() ); //expected # of sheets + assertEquals( 8, record.getRecordSize() ); //sid+size+data + record.validateSid((short)0x01AE); + } + /** * Tests that we can store the record * */ - public void testStore() - { - SupBookRecord record = new SupBookRecord(); - record.setFlag( (short) 0x401 ); - record.setNumberOfSheets( (short)0x4 ); - + public void testStoreIR() { + SupBookRecord record = SupBookRecord.createInternalReferences((short)4); + TestcaseRecordInputStream.confirmRecordEncoding(0x01AE, dataIR, record.serialize()); + } + + public void testStoreER() { + UnicodeString url = new UnicodeString("testURL"); + UnicodeString[] sheetNames = { + new UnicodeString("Sheet1"), + new UnicodeString("Sheet2"), + }; + SupBookRecord record = SupBookRecord.createExternalReferences(url, sheetNames); - byte [] recordBytes = record.serialize(); - assertEquals(recordBytes.length - 4, data.length); - for (int i = 0; i < data.length; i++) - assertEquals("At offset " + i, data[i], recordBytes[i+4]); + TestcaseRecordInputStream.confirmRecordEncoding(0x01AE, dataER, record.serialize()); } public static void main(String [] args) { @@ -84,6 +125,4 @@ public class TestSupBookRecord .println("Testing org.apache.poi.hssf.record.SupBookRecord"); junit.textui.TestRunner.run(TestSupBookRecord.class); } - - } diff --git a/src/testcases/org/apache/poi/hssf/record/TestcaseRecordInputStream.java b/src/testcases/org/apache/poi/hssf/record/TestcaseRecordInputStream.java index 767f507e7..ecb55ca82 100755 --- a/src/testcases/org/apache/poi/hssf/record/TestcaseRecordInputStream.java +++ b/src/testcases/org/apache/poi/hssf/record/TestcaseRecordInputStream.java @@ -1,4 +1,3 @@ - /* ==================================================================== Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with @@ -16,11 +15,12 @@ limitations under the License. ==================================================================== */ - - package org.apache.poi.hssf.record; import java.io.ByteArrayInputStream; + +import junit.framework.Assert; + import org.apache.poi.util.LittleEndian; /** @@ -33,6 +33,14 @@ import org.apache.poi.util.LittleEndian; public class TestcaseRecordInputStream extends RecordInputStream { + /** + * Convenience constructor + */ + public TestcaseRecordInputStream(int sid, byte[] data) + { + super(new ByteArrayInputStream(mergeDataAndSid((short)sid, (short)data.length, data))); + nextRecord(); + } public TestcaseRecordInputStream(short sid, short length, byte[] data) { super(new ByteArrayInputStream(mergeDataAndSid(sid, length, data))); @@ -46,4 +54,18 @@ public class TestcaseRecordInputStream System.arraycopy(data, 0, result, 4, data.length); return result; } + /** + * Confirms data sections are equal + * @param expectedData - just raw data (without sid or size short ints) + * @param actualRecordBytes this includes 4 prefix bytes (sid & size) + */ + public static void confirmRecordEncoding(int expectedSid, byte[] expectedData, byte[] actualRecordBytes) { + int expectedDataSize = expectedData.length; + Assert.assertEquals(actualRecordBytes.length - 4, expectedDataSize); + Assert.assertEquals(expectedSid, LittleEndian.getShort(actualRecordBytes, 0)); + Assert.assertEquals(expectedDataSize, LittleEndian.getShort(actualRecordBytes, 2)); + for (int i = 0; i < expectedDataSize; i++) + Assert.assertEquals("At offset " + i, expectedData[i], actualRecordBytes[i+4]); + + } } diff --git a/src/testcases/org/apache/poi/hssf/record/aggregates/TestValueRecordsAggregate.java b/src/testcases/org/apache/poi/hssf/record/aggregates/TestValueRecordsAggregate.java index a3315c297..8e8a72ece 100755 --- a/src/testcases/org/apache/poi/hssf/record/aggregates/TestValueRecordsAggregate.java +++ b/src/testcases/org/apache/poi/hssf/record/aggregates/TestValueRecordsAggregate.java @@ -17,15 +17,30 @@ package org.apache.poi.hssf.record.aggregates; -import junit.framework.TestCase; -import org.apache.poi.hssf.record.*; - +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.zip.CRC32; + +import junit.framework.AssertionFailedError; +import junit.framework.TestCase; + +import org.apache.poi.hssf.record.BlankRecord; +import org.apache.poi.hssf.record.FormulaRecord; +import org.apache.poi.hssf.record.Record; +import org.apache.poi.hssf.record.SharedFormulaRecord; +import org.apache.poi.hssf.record.UnknownRecord; +import org.apache.poi.hssf.record.WindowOneRecord; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; public class TestValueRecordsAggregate extends TestCase { + private static final String ABNORMAL_SHARED_FORMULA_FLAG_TEST_FILE = "AbnormalSharedFormulaFlag.xls"; ValueRecordsAggregate valueRecord = new ValueRecordsAggregate(); /** @@ -203,4 +218,117 @@ public class TestValueRecordsAggregate extends TestCase assertEquals( 36, valueRecord.getRecordSize() ); } + + /** + * Sometimes the 'shared formula' flag (FormulaRecord.isSharedFormula()) is set when + * there is no corresponding SharedFormulaRecord available. SharedFormulaRecord definitions do + * not span multiple sheets. They are are only defined within a sheet, and thus they do not + * have a sheet index field (only row and column range fields).
    + * So it is important that the code which locates the SharedFormulaRecord for each + * FormulaRecord does not allow matches across sheets.
    + * + * Prior to bugzilla 44449 (Feb 2008), POI ValueRecordsAggregate.construct(int, List) + * allowed SharedFormulaRecords to be erroneously used across sheets. That incorrect + * behaviour is shown by this test.

    + * + * Notes on how to produce the test spreadsheet:

    + * The setup for this test (AbnormalSharedFormulaFlag.xls) is rather fragile, insomuchas + * re-saving the file (either with Excel or POI) clears the flag.
    + *
      + *
    1. A new spreadsheet was created in Excel (File | New | Blank Workbook).
    2. + *
    3. Sheet3 was deleted.
    4. + *
    5. Sheet2!A1 formula was set to '="second formula"', and fill-dragged through A1:A8.
    6. + *
    7. Sheet1!A1 formula was set to '="first formula"', and also fill-dragged through A1:A8.
    8. + *
    9. Four rows on Sheet1 "5" through "8" were deleted ('delete rows' alt-E D, not 'clear' Del).
    10. + *
    11. The spreadsheet was saved as AbnormalSharedFormulaFlag.xls.
    12. + *
    + * Prior to the row delete action the spreadsheet has two SharedFormulaRecords. One + * for each sheet. To expose the bug, the shared formulas have been made to overlap.
    + * The row delete action (as described here) seems to to delete the + * SharedFormulaRecord from Sheet1 (but not clear the 'shared formula' flags.
    + * There are other variations on this theme to create the same effect. + * + */ + public void testSpuriousSharedFormulaFlag() { + File dataDir = new File(System.getProperty("HSSF.testdata.path")); + File testFile = new File(dataDir, ABNORMAL_SHARED_FORMULA_FLAG_TEST_FILE); + + long actualCRC = getFileCRC(testFile); + long expectedCRC = 2277445406L; + if(actualCRC != expectedCRC) { + System.err.println("Expected crc " + expectedCRC + " but got " + actualCRC); + throw failUnexpectedTestFileChange(); + } + HSSFWorkbook wb; + try { + FileInputStream in = new FileInputStream(testFile); + wb = new HSSFWorkbook(in); + } catch (IOException e) { + throw new RuntimeException(e); + } + + HSSFSheet s = wb.getSheetAt(0); // Sheet1 + + String cellFormula; + cellFormula = getFormulaFromFirstCell(s, 0); // row "1" + // the problem is not observable in the first row of the shared formula + if(!cellFormula.equals("\"first formula\"")) { + throw new RuntimeException("Something else wrong with this test case"); + } + + // but the problem is observable in rows 2,3,4 + cellFormula = getFormulaFromFirstCell(s, 1); // row "2" + if(cellFormula.equals("\"second formula\"")) { + throw new AssertionFailedError("found bug 44449 (Wrong SharedFormulaRecord was used)."); + } + if(!cellFormula.equals("\"first formula\"")) { + throw new RuntimeException("Something else wrong with this test case"); + } + } + private static String getFormulaFromFirstCell(HSSFSheet s, int rowIx) { + return s.getRow(rowIx).getCell((short)0).getCellFormula(); + } + + /** + * If someone opened this particular test file in Excel and saved it, the peculiar condition + * which causes the target bug would probably disappear. This test would then just succeed + * regardless of whether the fix was present. So a CRC check is performed to make it less easy + * for that to occur. + */ + private static RuntimeException failUnexpectedTestFileChange() { + String msg = "Test file '" + ABNORMAL_SHARED_FORMULA_FLAG_TEST_FILE + "' has changed. " + + "This junit may not be properly testing for the target bug. " + + "Either revert the test file or ensure that the new version " + + "has the right characteristics to test the target bug."; + // A breakpoint in ValueRecordsAggregate.handleMissingSharedFormulaRecord(FormulaRecord) + // should get hit during parsing of Sheet1. + // If the test spreadsheet is created as directed, this condition should occur. + // It is easy to upset the test spreadsheet (for example re-saving will destroy the + // peculiar condition we are testing for). + throw new RuntimeException(msg); + } + + /** + * gets a CRC checksum for the content of a file + */ + private static long getFileCRC(File f) { + CRC32 crc = new CRC32(); + byte[] buf = new byte[2048]; + try { + InputStream is = new FileInputStream(f); + while(true) { + int bytesRead = is.read(buf); + if(bytesRead < 1) { + break; + } + crc.update(buf, 0, bytesRead); + } + is.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + + return crc.getValue(); + } + } diff --git a/src/testcases/org/apache/poi/hssf/record/formula/AbstractPtgTestCase.java b/src/testcases/org/apache/poi/hssf/record/formula/AbstractPtgTestCase.java index 21e6a8ba3..0912b9761 100644 --- a/src/testcases/org/apache/poi/hssf/record/formula/AbstractPtgTestCase.java +++ b/src/testcases/org/apache/poi/hssf/record/formula/AbstractPtgTestCase.java @@ -1,4 +1,3 @@ - /* ==================================================================== Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with @@ -28,6 +27,7 @@ import junit.framework.TestCase; import org.apache.poi.hssf.model.Workbook; import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.poifs.filesystem.POIFSFileSystem; /** * Convenient abstract class to reduce the amount of boilerplate code needed @@ -35,8 +35,7 @@ import org.apache.poi.hssf.usermodel.HSSFWorkbook; * * @author Daniel Noll (daniel at nuix dot com dot au) */ -public abstract class AbstractPtgTestCase extends TestCase -{ +public abstract class AbstractPtgTestCase extends TestCase { /** Directory containing the test data. */ private static String dataDir = System.getProperty("HSSF.testdata.path"); @@ -51,16 +50,16 @@ public abstract class AbstractPtgTestCase extends TestCase throws IOException { File file = new File(dataDir, filename); InputStream stream = new BufferedInputStream(new FileInputStream(file)); - try - { - return new HSSFWorkbook(stream); - } - finally - { + // TODO - temp workaround to keep stdout quiet due to warning msg in POIFS + // When that warning msg is disabled, remove this wrapper and the close() call, + InputStream wrappedStream = POIFSFileSystem.createNonClosingInputStream(stream); + try { + return new HSSFWorkbook(wrappedStream); + } finally { stream.close(); } } - + /** * Creates a new Workbook and adds one sheet with the specified name */ @@ -73,5 +72,4 @@ public abstract class AbstractPtgTestCase extends TestCase book.setSheetName(0, sheetName); return book; } - } diff --git a/src/testcases/org/apache/poi/hssf/record/formula/AllFormulaTests.java b/src/testcases/org/apache/poi/hssf/record/formula/AllFormulaTests.java index b12681338..92ca4ba04 100644 --- a/src/testcases/org/apache/poi/hssf/record/formula/AllFormulaTests.java +++ b/src/testcases/org/apache/poi/hssf/record/formula/AllFormulaTests.java @@ -14,7 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. ==================================================================== */ - package org.apache.poi.hssf.record.formula; @@ -33,7 +32,8 @@ public class AllFormulaTests { result.addTestSuite(TestArea3DPtg.class); result.addTestSuite(TestAreaErrPtg.class); result.addTestSuite(TestAreaPtg.class); - result.addTestSuite(TestErrPtg.class); + result.addTestSuite(TestErrPtg.class); + result.addTestSuite(TestExternalFunctionFormulas.class); result.addTestSuite(TestFuncPtg.class); result.addTestSuite(TestIntersectionPtg.class); result.addTestSuite(TestPercentPtg.class); diff --git a/src/testcases/org/apache/poi/hssf/record/formula/TestAreaPtg.java b/src/testcases/org/apache/poi/hssf/record/formula/TestAreaPtg.java index 522a5bcf2..3a7f2f29a 100644 --- a/src/testcases/org/apache/poi/hssf/record/formula/TestAreaPtg.java +++ b/src/testcases/org/apache/poi/hssf/record/formula/TestAreaPtg.java @@ -27,15 +27,12 @@ import org.apache.poi.hssf.model.FormulaParser; * * @author Dmitriy Kumshayev */ -public class TestAreaPtg extends TestCase -{ +public final class TestAreaPtg extends TestCase { AreaPtg relative; AreaPtg absolute; - protected void setUp() throws Exception - { - super.setUp(); + protected void setUp() { short firstRow=5; short lastRow=13; short firstCol=7; @@ -64,10 +61,9 @@ public class TestAreaPtg extends TestCase } - public void resetColumns(AreaPtg aptg) - { - short fc = aptg.getFirstColumn(); - short lc = aptg.getLastColumn(); + private static void resetColumns(AreaPtg aptg) { + int fc = aptg.getFirstColumn(); + int lc = aptg.getLastColumn(); aptg.setFirstColumn(fc); aptg.setLastColumn(lc); assertEquals(fc , aptg.getFirstColumn() ); diff --git a/src/testcases/org/apache/poi/hssf/record/formula/TestExternalFunctionFormulas.java b/src/testcases/org/apache/poi/hssf/record/formula/TestExternalFunctionFormulas.java new file mode 100755 index 000000000..8c89dadea --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/record/formula/TestExternalFunctionFormulas.java @@ -0,0 +1,56 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hssf.record.formula; + +import java.io.FileInputStream; +import java.io.IOException; + +import junit.framework.TestCase; + +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +/** + * Tests for functions from external workbooks (e.g. YEARFRAC). + * + * + * @author Josh Micich + */ +public final class TestExternalFunctionFormulas extends TestCase { + + + /** + * tests NameXPtg.toFormulaString(Workbook) and logic in Workbook below that + */ + public void testReadFormulaContainingExternalFunction() { + String filePath = System.getProperty("HSSF.testdata.path")+ "/" + + "externalFunctionExample.xls"; + HSSFWorkbook wb; + try { + FileInputStream fin = new FileInputStream(filePath); + wb = new HSSFWorkbook( fin ); + } catch (IOException e) { + throw new RuntimeException(e); + } + + String expectedFormula = "YEARFRAC(B1,C1)"; + HSSFSheet sht = wb.getSheetAt(0); + String cellFormula = sht.getRow(0).getCell((short)0).getCellFormula(); + assertEquals(expectedFormula, cellFormula); + } + +} diff --git a/src/testcases/org/apache/poi/hssf/usermodel/AllUserModelTests.java b/src/testcases/org/apache/poi/hssf/usermodel/AllUserModelTests.java new file mode 100755 index 000000000..cbc555da3 --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/usermodel/AllUserModelTests.java @@ -0,0 +1,71 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hssf.usermodel; + +import junit.framework.Test; +import junit.framework.TestSuite; + +/** + * Collects all tests for the org.apache.poi.hssf.usermodel package. + * + * @author Josh Micich + */ +public class AllUserModelTests { + + public static Test suite() { + TestSuite result = new TestSuite("Tests for org.apache.poi.hssf.usermodel"); + + result.addTestSuite(TestBugs.class); + result.addTestSuite(TestCellStyle.class); + result.addTestSuite(TestCloneSheet.class); + result.addTestSuite(TestDataValidation.class); + result.addTestSuite(TestEscherGraphics.class); + result.addTestSuite(TestEscherGraphics2d.class); + result.addTestSuite(TestFontDetails.class); + result.addTestSuite(TestFormulas.class); + result.addTestSuite(TestHSSFCell.class); + result.addTestSuite(TestHSSFClientAnchor.class); + result.addTestSuite(TestHSSFComment.class); + result.addTestSuite(TestHSSFDateUtil.class); + result.addTestSuite(TestHSSFHeaderFooter.class); + result.addTestSuite(TestHSSFHyperlink.class); + result.addTestSuite(TestHSSFPalette.class); + result.addTestSuite(TestHSSFPicture.class); + result.addTestSuite(TestHSSFPictureData.class); + result.addTestSuite(TestHSSFRichTextString.class); + result.addTestSuite(TestHSSFRow.class); + result.addTestSuite(TestHSSFSheet.class); + result.addTestSuite(TestHSSFSheetOrder.class); + result.addTestSuite(TestHSSFSheetSetOrder.class); + result.addTestSuite(TestHSSFWorkbook.class); + result.addTestSuite(TestNamedRange.class); + result.addTestSuite(TestOLE2Embeding.class); + result.addTestSuite(TestReadWriteChart.class); + result.addTestSuite(TestSanityChecker.class); + result.addTestSuite(TestSheetHiding.class); + result.addTestSuite(TestSheetShiftRows.class); + if (false) { // deliberately avoiding this one + result.addTestSuite(TestUnfixedBugs.class); + } + result.addTestSuite(TestUnicodeWorkbook.class); + result.addTestSuite(TestUppercaseWorkbook.class); + result.addTestSuite(TestWorkbook.class); + + return result; + } +} diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java b/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java index 6dfdddad4..f9bb362c7 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java @@ -1089,6 +1089,44 @@ extends TestCase { // "EmptyStackException" //assertEquals("=CHOOSE(2,A2,A3,A4)", c2.getCellFormula()); } + + /** + * Crystal reports generates files with short + * StyleRecords, which is against the spec + */ + public void test44471() throws Exception { + FileInputStream in = new FileInputStream(new File(cwd, "OddStyleRecord.xls")); + + // Used to blow up with an ArrayIndexOutOfBounds + // when creating a StyleRecord + HSSFWorkbook wb = new HSSFWorkbook(in); + in.close(); + + assertEquals(1, wb.getNumberOfSheets()); + } + + /** + * Files with "read only recommended" were giving + * grief on the FileSharingRecord + */ + public void test44536() throws Exception { + FileInputStream in = new FileInputStream(new File(cwd, "ReadOnlyRecommended.xls")); + + // Used to blow up with an IllegalArgumentException + // when creating a FileSharingRecord + HSSFWorkbook wb = new HSSFWorkbook(in); + in.close(); + + // Check read only advised + assertEquals(3, wb.getNumberOfSheets()); + assertTrue(wb.isWriteProtected()); + + // But also check that another wb isn't + in = new FileInputStream(new File(cwd, "SimpleWithChoose.xls")); + wb = new HSSFWorkbook(in); + in.close(); + assertFalse(wb.isWriteProtected()); + } } diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestDataValidation.java b/src/testcases/org/apache/poi/hssf/usermodel/TestDataValidation.java index f970ff26f..34885e7a2 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestDataValidation.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestDataValidation.java @@ -51,7 +51,7 @@ public class TestDataValidation extends TestCase public void testDataValidation() throws Exception { System.out.println("\nTest no. 2 - Test Excel's Data validation mechanism"); - String resultFile = System.getProperty("HSSF.testdata.path")+"/TestDataValidation.xls"; + String resultFile = System.getProperty("java.io.tmpdir")+File.separator+"TestDataValidation.xls"; HSSFWorkbook wb = new HSSFWorkbook(); HSSFCellStyle style_1 = this.createStyle( wb, HSSFCellStyle.ALIGN_LEFT ); diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestFormulas.java b/src/testcases/org/apache/poi/hssf/usermodel/TestFormulas.java index f1e8f4977..9eb12bd4e 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestFormulas.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestFormulas.java @@ -302,10 +302,10 @@ extends TestCase { } c = r.getCell((short) y); - CellReference cr= new CellReference(refx1,refy1); - ref=cr.toString(); - cr=new CellReference(refx2,refy2); - ref2=cr.toString(); + CellReference cr= new CellReference(refx1,refy1, false, false); + ref=cr.formatAsString(); + cr=new CellReference(refx2,refy2, false, false); + ref2=cr.formatAsString(); c = r.createCell((short) y); c.setCellFormula("" + ref + operator + ref2); @@ -379,10 +379,10 @@ extends TestCase { } c = r.getCell((short) y); - CellReference cr= new CellReference(refx1,refy1); - ref=cr.toString(); - cr=new CellReference(refx2,refy2); - ref2=cr.toString(); + CellReference cr= new CellReference(refx1, refy1, false, false); + ref=cr.formatAsString(); + cr=new CellReference(refx2,refy2, false, false); + ref2=cr.formatAsString(); assertTrue("loop Formula is as expected "+ref+operator+ref2+"!="+c.getCellFormula(),( diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFPalette.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFPalette.java index c5674b9e7..a0f09696b 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFPalette.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFPalette.java @@ -160,6 +160,49 @@ public class TestHSSFPalette extends TestCase assertEquals("FFFF:0:FFFF", p.getColor((short)14).getHexString()); } + public void testFindSimilar() throws Exception { + HSSFWorkbook book = new HSSFWorkbook(); + HSSFPalette p = book.getCustomPalette(); + + + // Add a few edge colours in + p.setColorAtIndex((short)8, (byte)-1, (byte)0, (byte)0); + p.setColorAtIndex((short)9, (byte)0, (byte)-1, (byte)0); + p.setColorAtIndex((short)10, (byte)0, (byte)0, (byte)-1); + + // And some near a few of them + p.setColorAtIndex((short)11, (byte)-1, (byte)2, (byte)2); + p.setColorAtIndex((short)12, (byte)-2, (byte)2, (byte)10); + p.setColorAtIndex((short)13, (byte)-4, (byte)0, (byte)0); + p.setColorAtIndex((short)14, (byte)-8, (byte)0, (byte)0); + + assertEquals( + "FFFF:0:0", p.getColor((short)8).getHexString() + ); + + // Now check we get the right stuff back + assertEquals( + p.getColor((short)8).getHexString(), + p.findSimilarColor((byte)-1, (byte)0, (byte)0).getHexString() + ); + assertEquals( + p.getColor((short)8).getHexString(), + p.findSimilarColor((byte)-2, (byte)0, (byte)0).getHexString() + ); + assertEquals( + p.getColor((short)8).getHexString(), + p.findSimilarColor((byte)-1, (byte)1, (byte)0).getHexString() + ); + assertEquals( + p.getColor((short)11).getHexString(), + p.findSimilarColor((byte)-1, (byte)2, (byte)1).getHexString() + ); + assertEquals( + p.getColor((short)12).getHexString(), + p.findSimilarColor((byte)-1, (byte)2, (byte)10).getHexString() + ); + } + /** * Verifies that the generated gnumeric-format string values match the * hardcoded values in the HSSFColor default color palette diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFPicture.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFPicture.java index 25d6684c4..fd01001f8 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFPicture.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFPicture.java @@ -16,25 +16,28 @@ */ package org.apache.poi.hssf.usermodel; -import junit.framework.TestCase; - -import java.io.IOException; -import java.io.FileInputStream; import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +import junit.framework.TestCase; /** * Test HSSFPicture. * * @author Yegor Kozlov (yegor at apache.org) */ -public class TestHSSFPicture extends TestCase{ +public final class TestHSSFPicture extends TestCase{ - public void testResize() throws Exception { + public void testResize() { HSSFWorkbook wb = new HSSFWorkbook(); HSSFSheet sh1 = wb.createSheet(); HSSFPatriarch p1 = sh1.createDrawingPatriarch(); - int idx1 = loadPicture( "src/resources/logos/logoKarmokar4.png", wb); + byte[] pictureData = getTestDataFileContent("logoKarmokar4.png"); + int idx1 = wb.addPicture( pictureData, HSSFWorkbook.PICTURE_TYPE_PNG ); HSSFPicture picture1 = p1.createPicture(new HSSFClientAnchor(), idx1); HSSFClientAnchor anchor1 = picture1.getPreferredSize(); @@ -52,28 +55,25 @@ public class TestHSSFPicture extends TestCase{ /** * Copied from org.apache.poi.hssf.usermodel.examples.OfficeDrawing */ - private static int loadPicture( String path, HSSFWorkbook wb ) throws IOException - { - int pictureIndex; - FileInputStream fis = null; - ByteArrayOutputStream bos = null; - try - { - fis = new FileInputStream( path); - bos = new ByteArrayOutputStream( ); - int c; - while ( (c = fis.read()) != -1) - bos.write( c ); - pictureIndex = wb.addPicture( bos.toByteArray(), HSSFWorkbook.PICTURE_TYPE_PNG ); - } - finally - { - if (fis != null) - fis.close(); - if (bos != null) - bos.close(); - } - return pictureIndex; - } + private static byte[] getTestDataFileContent(String fileName) { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + String readFilename = System.getProperty("HSSF.testdata.path"); + try { + InputStream fis = new FileInputStream(readFilename+File.separator+fileName); + + byte[] buf = new byte[512]; + while(true) { + int bytesRead = fis.read(buf); + if(bytesRead < 1) { + break; + } + bos.write(buf, 0, bytesRead); + } + fis.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + return bos.toByteArray(); + } } diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFRow.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFRow.java index 44c03cd20..b6f22022c 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFRow.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFRow.java @@ -1,4 +1,3 @@ - /* ==================================================================== Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with @@ -15,34 +14,22 @@ See the License for the specific language governing permissions and limitations under the License. ==================================================================== */ - package org.apache.poi.hssf.usermodel; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; + import junit.framework.TestCase; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; - -import org.apache.poi.util.TempFile; - /** * Test HSSFRow is okay. * * @author Glen Stampoultzis (glens at apache.org) */ -public class TestHSSFRow - extends TestCase -{ - public TestHSSFRow(String s) - { - super(s); - } +public final class TestHSSFRow extends TestCase { - public void testLastAndFirstColumns() - throws Exception - { + public void testLastAndFirstColumns() { HSSFWorkbook workbook = new HSSFWorkbook(); HSSFSheet sheet = workbook.createSheet(); HSSFRow row = sheet.createRow((short) 0); @@ -51,127 +38,146 @@ public class TestHSSFRow row.createCell((short) 2); assertEquals(2, row.getFirstCellNum()); - assertEquals(2, row.getLastCellNum()); + assertEquals(3, row.getLastCellNum()); row.createCell((short) 1); assertEquals(1, row.getFirstCellNum()); - assertEquals(2, row.getLastCellNum()); - + assertEquals(3, row.getLastCellNum()); + // check the exact case reported in 'bug' 43901 - notice that the cellNum is '0' based row.createCell((short) 3); assertEquals(1, row.getFirstCellNum()); - assertEquals(3, row.getLastCellNum()); - + assertEquals(4, row.getLastCellNum()); } - public void testRemoveCell() - throws Exception - { + public void testRemoveCell() throws Exception { HSSFWorkbook workbook = new HSSFWorkbook(); HSSFSheet sheet = workbook.createSheet(); HSSFRow row = sheet.createRow((short) 0); assertEquals(-1, row.getLastCellNum()); assertEquals(-1, row.getFirstCellNum()); row.createCell((short) 1); - assertEquals(1, row.getLastCellNum()); + assertEquals(2, row.getLastCellNum()); assertEquals(1, row.getFirstCellNum()); row.createCell((short) 3); - assertEquals(3, row.getLastCellNum()); + assertEquals(4, row.getLastCellNum()); assertEquals(1, row.getFirstCellNum()); row.removeCell(row.getCell((short) 3)); - assertEquals(1, row.getLastCellNum()); + assertEquals(2, row.getLastCellNum()); assertEquals(1, row.getFirstCellNum()); row.removeCell(row.getCell((short) 1)); assertEquals(-1, row.getLastCellNum()); assertEquals(-1, row.getFirstCellNum()); - // check the row record actually writes it out as 0's + // all cells on this row have been removed + // so check the row record actually writes it out as 0's byte[] data = new byte[100]; row.getRowRecord().serialize(0, data); assertEquals(0, data[6]); assertEquals(0, data[8]); - File file = TempFile.createTempFile("XXX", "XLS"); - FileOutputStream stream = new FileOutputStream(file); - workbook.write(stream); - stream.close(); - FileInputStream inputStream = new FileInputStream(file); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + workbook.write(baos); + baos.close(); + ByteArrayInputStream inputStream = new ByteArrayInputStream(baos.toByteArray()); workbook = new HSSFWorkbook(inputStream); sheet = workbook.getSheetAt(0); - stream.close(); - file.delete(); + inputStream.close(); + assertEquals(-1, sheet.getRow((short) 0).getLastCellNum()); assertEquals(-1, sheet.getRow((short) 0).getFirstCellNum()); } - - public void testMoveCell() throws Exception { + + public void testMoveCell() { HSSFWorkbook workbook = new HSSFWorkbook(); HSSFSheet sheet = workbook.createSheet(); HSSFRow row = sheet.createRow((short) 0); HSSFRow rowB = sheet.createRow((short) 1); - + HSSFCell cellA2 = rowB.createCell((short)0); assertEquals(0, rowB.getFirstCellNum()); assertEquals(0, rowB.getFirstCellNum()); - + assertEquals(-1, row.getLastCellNum()); assertEquals(-1, row.getFirstCellNum()); HSSFCell cellB2 = row.createCell((short) 1); HSSFCell cellB3 = row.createCell((short) 2); HSSFCell cellB4 = row.createCell((short) 3); - + assertEquals(1, row.getFirstCellNum()); - assertEquals(3, row.getLastCellNum()); - + assertEquals(4, row.getLastCellNum()); + // Try to move to somewhere else that's used try { - row.moveCell(cellB2, (short)3); - fail(); - } catch(IllegalArgumentException e) {} - + row.moveCell(cellB2, (short)3); + fail("IllegalArgumentException should have been thrown"); + } catch(IllegalArgumentException e) { + // expected during successful test + } + // Try to move one off a different row try { - row.moveCell(cellA2, (short)3); - fail(); - } catch(IllegalArgumentException e) {} - + row.moveCell(cellA2, (short)3); + fail("IllegalArgumentException should have been thrown"); + } catch(IllegalArgumentException e) { + // expected during successful test + } + // Move somewhere spare assertNotNull(row.getCell((short)1)); - row.moveCell(cellB2, (short)5); + row.moveCell(cellB2, (short)5); assertNull(row.getCell((short)1)); assertNotNull(row.getCell((short)5)); - - assertEquals(5, cellB2.getCellNum()); + + assertEquals(5, cellB2.getCellNum()); assertEquals(2, row.getFirstCellNum()); - assertEquals(5, row.getLastCellNum()); - + assertEquals(6, row.getLastCellNum()); } - - public void testRowBounds() - throws Exception - { + + public void testRowBounds() { HSSFWorkbook workbook = new HSSFWorkbook(); HSSFSheet sheet = workbook.createSheet(); //Test low row bound - HSSFRow row = sheet.createRow( (short) 0); - //Test low row bound exception - boolean caughtException = false; + sheet.createRow( (short) 0); + //Test low row bound exception try { - row = sheet.createRow(-1); + sheet.createRow(-1); + fail("IndexOutOfBoundsException should have been thrown"); } catch (IndexOutOfBoundsException ex) { - caughtException = true; - } - assertTrue(caughtException); - //Test high row bound - row = sheet.createRow(65535); - //Test high row bound exception - caughtException = false; + // expected during successful test + } + + //Test high row bound + sheet.createRow(65535); + //Test high row bound exception try { - row = sheet.createRow(65536); + sheet.createRow(65536); + fail("IndexOutOfBoundsException should have been thrown"); } catch (IndexOutOfBoundsException ex) { - caughtException = true; - } - assertTrue(caughtException); + // expected during successful test + } + } + + /** + * Prior to patch 43901, POI was producing files with the wrong last-column + * number on the row + */ + public void testLastCellNumIsCorrectAfterAddCell_bug43901(){ + HSSFWorkbook book = new HSSFWorkbook(); + HSSFSheet sheet = book.createSheet("test"); + HSSFRow row = sheet.createRow(0); + + // New row has last col -1 + assertEquals(-1, row.getLastCellNum()); + if(row.getLastCellNum() == 0) { + fail("Identified bug 43901"); + } + + // Create two cells, will return one higher + // than that for the last number + row.createCell((short) 0); + assertEquals(1, row.getLastCellNum()); + row.createCell((short) 255); + assertEquals(256, row.getLastCellNum()); } - } diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestNamedRange.java b/src/testcases/org/apache/poi/hssf/usermodel/TestNamedRange.java index f22de1758..afbbde026 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestNamedRange.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestNamedRange.java @@ -578,18 +578,16 @@ public class TestNamedRange // retrieve the cell at the named range and test its contents AreaReference aref = new AreaReference(aNamedCell.getReference()); - CellReference[] crefs = aref.getCells(); - assertNotNull(crefs); - assertEquals("Should be exactly 1 cell in the named cell :'" +cellName+"'", 1, crefs.length); - for (int i=0, iSize=crefs.length; i)jjK8b=HB^ zI;yy;bt~>I;KG64|M%p%k6f0b_5HkWfBzdePm+`5Bsob=cJ7JeWil3SsB(=JP%W4a zd#5sFdK!2c%z|V$WAMQk;ohlKDhZtm1FTy0FX6yjq?5|f6fg|n0-!S22N(bh0Y(60 zfC+%g+6sDU0IC2h11Jk92PhAy0H_Ecf9B9D1F8Tl0966i0G0r9cZOaaU=6SV)Bw~3 z)B@B7ke|BH>jCNm8UPvsYyn>Z$lVUQJ-`9r2yg;40yGAYyC3vUfTnSr0JHE_MX4;si+VYf|2A*AuIa}g zv3oHk>aP##u?N75w?R!mA2ORWLC}QpXJi zZ-5WL7oaKs{a|kc(3Jl~Lz4ewrv8ESqU1lxoRY|Y3LgQ#k$_K?|J27&c#{9r4^bbW zDgTGTPJIB$f9e}j0VsbNm9wNMopP!SP-*Kx}wi5+9oQ2NDh_UZ=@yE4kY`vu7?FsgEpH z$#i%dDAxTW4h8ww75h%z{3ftD7}1do6;+-|!f16Z z2mX^BaKmss8#56%X7axB>0zWzqr+JAkTmusIb_Z}Tbr|>mV!JOdTh(e@-YBkW){05nN7wSzeU5Xry&A$1k#0pj^+m_l^G82qVf6ZAYP zCK|oAqChIEvPv%?Kpp?qV}g-RjA+mWCYaDNK9j105$`kAUh3iAr%a#IQJ|NJ#%P{o zIptSUBt1&0du@n#732ZPp#H2Wb@H@gO76IX+Fab|ea z3^S9$QCCYURSjf@`dl{(s~Jbs5AS2(xu)Eyl~5K2ee};!;7d&Yagsg{M}(j>!oZd) z&@?s)T^nbx+6;|PZwXJMWl~Z)YQapS4rxR|GM9L!3!j<*6rcSPPYe1Xw~x^yY6hSw z0fnJ>2#+!aP=9<2K#J^F9duYb^ET}4&;&Nu<`PS9dyA>uD8JII_vPegrD+`ONI{?^ zU8d#@uNE01WnN_{x=yI#{Q&V8EW~jvMJy??ictlliYN|jHO;8Y%(voX;V6^I^mKIP zx-uR4ci%7t(5Vs~UQ2=Kh@y){vrB|?Tj+zJ)5Mb+Hce)oKu5-Obp@HAuP4*hEE%|R zl}g`03}<5bRFTAiLoFV=v7TH;s@T}w(XgWt6NRw`L$eLWnzO9^XaP~gL-u0T-dZ;| z?e!tEF0XzVkMQ~^@Fgxe(=nrBD2OHh7#QiWEaEaJ#WC7X$vLo8FHZg|`<0Ctks#Gs zbzqtVe#%6!s5nu?Y9$EFjmk-?nG!y~-m)Fp>t?C2x+SlD^lAU;3c}N_YC$FCUrJv=cL`sd zPINVuL{HMCaFQR%{uJS8r)7dVd_kT35#I~aDQGADXex=0CaxrADP13j-o?^F83M17&d*rt&kWx6wT}rnh%3TR0)jv{_zZJB#1JxGc ziLa7fa_5d;;3LtMNJ;#mcp3mH zXjA=39FWU#bWgBnc9WxoCjCh`SgrrjR-Wpd z0Vb1a<4mjmuW;ZUR;;k^S|~dt95TM0|KwFbl-a5u4Oso!o6ghhv_BlmKqvRf018Q` z05F+hO8_*ZL3QDK+54f>WcCDr_H-`+(8aKa06wV&5!xoBc?`|u8bGHRv@>)%XNV;w zK2vH3omP&zLN|ln2YNZ^w0%qQ(>9$Ybc&l)K{L@D7!sW@WI(_8fx(5{HN#Q*+Quiu z2FHm0iVI8U%qbpoK#^zEfOiA131fUobE8j~)rJOG8Q^B;V_j&KfcS=NaQ+bw$gWiD z(6ma+0WAl#9MEz=%KecKK+6Fw2ecf}azM+0FTw%cLMLB7?L3MzX5e|JUrKiV zMnNg3i9FPQir)+8Zju0>t8o95uT{|cgtRmJF*Eh^MIlTJISPpf#e&06J?# z>qB%tYXQIv@C$&}Z`K3oB*``at@oq>h;}+ab6v*(cK`4MUP!b)LhCi8OF+lO$-K(S zn8yQSvAQJ^KxrpnCa_RMw2(sy?--pF6V6F-XJE8S%KecKK+6Fw2ecf}azM)g zEeEt5&~iY_0WAl#9MEz=%KecKK+6Fw2ecf}azM)gEeEt5&~iY_fqw-D#^8xS z?fb9)ilX{Iok%zKA9?@P?Tfts>h>Ra|JAMJ@4tGn?rs8mg0o8WHg4ZRPf!z#@NR7k z&Migbe!c<^Y=+~t?8M13xZ&}uh27t{a;kia#5Mu^OGR7k>0Z|3(*SlR&oo@_Y2 z(rdOwzbji7Z^{yYA^en?SYUMUP5Q|S9W$zmRE&98sd8%Hl?7WqTcyg2dsmj4fYb~= zmwcXJ5gbOfFX_9ob?~k%O5RnB6^AxciUU!^Yu}aq-158=9};7Xc*dMEWuzW++`()_NZ z@q5CrbpHdUfAsrFZvW-qPjW8N?9zn@g_x8rDXl=rrWZ$fbYD1~QKa6%HV z!M+~R5fM(DkN$z&(Uv=w;5Vlg=!Dc@B+G*I^5^# zClH<{3a8X`N}|_*e_x14djEGF#1y^%`vT#Mny^UxkkSXQ`ILk&PA9sWN}?y}QaH(v zWH$zXsHA^UhcBp;KYHi3Af1AC;*X}1=xE|fVwTeNF?texDIJntk~oU;Bhi!McSe4w z9<6`!*UqyV@Bd2muMgZc`6khn(p?>Vr3Esof21V({CRKK2deoh*(G=G`3rm`x)Ldg zKNL?LAfkwCW#npgLaWb&@;c z)!(-AR(Q471#kY+Pju04f+=ePHy8O-yyWW)yjJ}m%G9zhEGNQjm3Mv{LSA2e)C=eCJOJvnrKqgqls{N zlr@z~xeMkf6r5L9Q}R&p1OpY=7s z=?N+BD#cL9xcItW4$fUjx~4aOw&a}gJZqu z@)%++c&W@%-W0dvEHne#;#y0ATW1;p^SfHgz463};zPlUOF=}axAptGb7ENfx}o42GD~?sR=4Qrf^gj~h}m*)MMan2J%4nC-K6M- zg7Zy_%3Xdoi0P!F%FlzMeC3bYmAQT(w?kR|4@I9p-Ni+p-&f)23%iTCw?pMzK@uJIvHkeB>B|!@mo~(W3+UM ze9)@2c$<@@YToATV`ju!o2@ij0CydhXA^L*bG1yyoY@q19bZx;fVc=!e@Gs23=FB{ zLH$XEo=O%N($dnPrLtMGRBV<*1ZW&(N8vvLe=6Wv1nftNU@E6YG~o$0`Xe-bfrK9l zcNF1Os#$Cn?5bG^3%aVKvzSAKia8uT3Osxh%mPG!e8em^efo3;56oc}5DxeU%;Er? z1HuB))2tM&@*<)}jT%wBtb>`H33$rL0ecA(7|f77wajD?UV(6-+f^O8uE#o=>A@9B zkn@cPZq{dZiAjp~NeLp3S0#OB9~YK~qoM&(hXf5AQ3lR2DN6K*yS0SDlDbe<-a_s! z_rSM_TnEoViSPU%hol_cL==xlL^V~Tcr=_wYG*Zy$0GW2bd%#M4i83o>JpxqM3iJ= zyt8MKYQclaL|^Ggq1t$=QJlDla#y1`aWSAPh*x0h1n3E+5-OLPFDWR!w#dWJib6(#WyJ_(!PM^2H zjW|Jo7vdOaXIGRX^=f69j!0WTB1IEjofo*X%uKzD#CdlP-^si(EC8E`P8cGDUk*BE zCJ0ZBMwer#(zGXcfg%!^DW{dc49Q(kyOUYQ&0;tmwOJ2%-XuVUXa@0=Q5wD9n?4*j zen1jwL(vLd(6uJw;#FR}GHxzDUXfd$7cVZJtMjZ`>UnYF`M3|}S*&fv#qEWrM-MI3 z02gGerkR4tWGD$V-))dmQ#6K38<;;kfjudr6kt{!zSVUdp(RDlT1<~vUg84^L|f;J z(AL$c3nFbF&=#1x89i(<8E^@(7Bc!S?!jrMw4hrx9zsgDq*Dk5)h6{{h}xG}H$`8W z%+kyfc}F845nDjnfJ=;3=Q-9#eIb!8s!GfVoV!Cubk}il}s>kZI8zQ}Vsnx_%YPkr9PvS>g_@wqy9%WyYqV!gR0A~!H1hYnW zD5=t{2|9)H5Qdf*l7<0YkyQYdOXy+)aXt8Z2#i~hLpm6uOJ$}D4omJ5osZSd^|+ER zg)gdhQajPCm-f8d(cxhV@v;(#VF8Ew<5A0FhAFE8KRLHr061*Vq;GYy4K zLtjsg4C+4Y8GZo!TsU~D*nUDgQp>w3)wW}p}9akI5qdbkfI$zX}W9QF)BK- zOMF7Vkofq1bKqEtRpyDNNhuwpr|&vWVkiI7AS^>@#(FeeXeSPs1lCpN#{Es?Uu*s? zg|EQ2@-P=Th6a^~Kuwi0nK!7Ttl*IIKjmu6vXB<_P**t0N(&wF=w~eNDWvxY6_}L8 z0~d_PKKM@hY_hQSj79jLsHP~uJLo6cE(v0SMhm@+TtQfUgplPKi)XC=dvJTA(Aad@Iyl>h(BcmZXXrzAet7y0l~a*^THwNIZrOMbe?v@$@UolyOtIDHs#eiJ_Q?$1q7$BA)2C7Zf6jf729G zWtfhq>ZnLBld8w{vZ->emrK>>dihjCu2)Dk;5tiXT(6j_tLnph?QhZVtfe(kW+YU{ zjNDDGlsCuB)(5%*edMnmK zmJcw8lvhIUN_C%UL|W9a0Pvq z*Lbfg5}rK)`VUL7rmGBD2o2=l4ZiTG>+m)|buv#marW146I=cq`__HT?FP@L^cnW@ z$J8a!wFXYSc5X?Qv3HF=J&oryh`8qKm*fywzsIlE5#6GfZJLwbY5MT(4IJu*Pb`1( zP_V)%;CSYxoSi#!o?IDv@`>y5TL)co<~(h&=jxDSiN}Uy8rm#s_GFoIZ&mlQ1C|E9 z9oPGwUwN18A03~WD<|LWydu*(dBvnOMZI!1Ip4l&6mE6jv-aN>jxGOeJZr$9nc)%3 zJPvOiQ|@wX-hrK=-3K{#srCF;GK@`YH~jUqRgQbR%Mx4exsUHCF!&-PPCu6}*~)Owf5dV4Z{ zxN~Ru!8*hCME<-q_{_A04`(@!uC&aj-qAKy4n6;_wBBi!oaLMydO0e&OrL0`x^^bO z?)uZsmG@24d-PqUzkFAI6UCk!ymYKb=bz-ybYAYgamaO0v&N^_Zhg5~Z~u*xGg_~C zKFMyM{)zQ=4kKKT{LraN-({t5`3LS8ma#<0Jy(0Z@2jVo$NaBt%G`6*%J%1j^=4H` zONsH*O?fk+NA|uH^TyYO-FsU+JGt82Z`6wsfz48_-gKJu(%&@8x7YK&Ejrlsh<>@U z#^on_du$JU^>k6vAM2dX4bNC73@!gGWZjE9SLdsgjWaLweKM$f*|bSRTsHQ#ObKl9 zb4u6g3x}o7938*gHvRf=m(VlK?rvXyJuSZ1vpMq{n!YTRyg%dE?Sh26b$3^S~~rzNV>Gp?a>lLc=Dt(k3Mu5C65aWx%+O zElyPFAFpg_3ZKRvW3^akia(dpIhnAN%gY z;+pQIpSkNKq)&2MIN$eDUsY7{&VVu3&xYzxGyTi*woclG{XP54vun$CR!itT$~YX8 zp9ePPs&zjl{`Sf>>Hf3xs~+81_SYMozwfAyq=X}ZgJNnY?|jp{TFP;!XVYBG%rIZQ zk~`wuGyT8U=)KHtTH7q8PM=Aq(uU~vnaH|Y4gOA+Wjih>nr+!TetYQy@%q=@b^3Yk zv&?**yWyZq>MrxNra$Q&i)p%6b^Up*2UkX}=rX$70q^}L?(Q?U4s#D`HZyw7?iGV4 z>-~1mu)CXI)P|c~W-O{RG`CfytQXDO%VT0jP8nsZnrCV?cG)j=nsgs_Xme<*xxQ!b zTz=8y(bUXqkv6BR1-KqQ++gvea~-U=o=d89ddQ2`mlwV3-EqDUAdkIcP$elYw%gNX zEA1OxbC?o;xo4+kPUWw*Y+Y{5E}th?xBN7tdi?aAe#_TgsTDnGf=#9S3oA9Lx6p6m z%)1AVR++J3*8!*HqhBwK8=IaJmusnfWYQ>ejnB;3j>!XM;f>p#h~2hs_WfP+HlAws zblwU#Wtw?pjVg5ynA;pP?^na(WYQkR-<|zhr4&G;oSYFb$>_=5=y2ml*~_M%xRJB*-ktu- zx3rz?F>ac7<&cyrOTT|^@=crF0TriP*PGt;_|)S)>rLCyq4nhGAAX!PK1A2By#L~w z2Wxjfv;0(@x_^vMcbr|P=CdCHA8qK+v~_jwc|B_V-OPTr$E@t0i9Kybhp%vZV7&EJ z!)aD=dtavSz83K+Xvo9afz*%Pr+!g>`{a((zI}%U{O)mN$d-yT@2c1V;0yL+yMZg{ z^Cll5h`n8P1lSAXf{S%o+8qlv0sg5UJJbC^Ku_=T^LyDYEq&4PV)t&bb=PzqH)%)3 z>Z8xeLVlcis`C-Q8$MyWL!t-ITx57|^OA05*B>5Jdh`~rez|>eEm|J2O0zn#KRIf` zBmK~FL2vt<3VWM=$zf~ccSEn)KJ0YqX4)uRNXvKS}R=BKZE1j5~jCof~t~wc)-y z8HtJSVskBwT9r1=W;?d2D(<^~b>p11y(f%qkv-!3^FGP;J@qZ`4b1Ib&oZLaN`rBc zGc2Yyj#pWh8Gm)$xs0GP*`_Z`AFn^vOSpTiT+Iy&9lGqde?7nTn9x0s>W}-W!i@b( z{4eUKSiPz6w$k-)?dnF(-C@0?)P!Nbc^=Stw9M7!`q3PZdxKvp7X4E0(S=p_vU~U? z-f&3q@>0!f7qdQU`Cy&19bL!PS$);pW5bgp&w~yPv|C%DUc%5nEOY0+-Z#DSBGv1h zGSB{0RZ%(Id}DT_OwN)ndwM+FzPifh~VOx&$d%3g2vI8@>RypNl{^QdlTc)q8{>ozX)^^XAPJ3wTUH422w}h>3 zrVDCk9RE7k)q41m+Fdf6;{w{@Vs$vTiW1Q;ja3gOhsPxbcmT$*W+`uKjj3(@y((ib`*ul4=PV}>xZ&xqu?5y3-%h?=%9<_ho z!Q-pl)6)-EaB6wH;lV`{g6dzfFXfp~djGI1Y^|^=r-o14-&0Iim+C$`IJw2tZ}-|O z`!%ajp+YIkArt52IG_1WuTkdL{SThJxFD+a=2^+Lmp8m!ZenE8v)e|^s*I{S-hN)_ zi7k_yw_F@}cBEs)bv=U~wdix@;OzOUvnJRbtaoeCgtJ|LcDuZw@vJ?w)11axJ{vHq z!^&OOZ7VdN_qEZ`KMc=i<=$QRYSYujzUh~jysUaXY03GfZOuihIYf27Ji5WFu00Ni zO^WR4(z$M>@SO*~sdC!mj?trae`T~C-}%|*)DA0SU)P-4?bwj!hx-jq{`FMu`Bx`b z-fppGuXl! z2gPiClx($R!_Cm(St)s@V{n|fDH zUw5aPUgHT@?7Rb4Rtvjq5wT^43m7Kxls8wYWDRG;`m?}o4r=;g@W^wLG3)Y!V>+on; zW=`0!CQjKkv3Hd3tQ` zR$bH-1_|fJd`EbJ~dOKDRpT0Bbt0pNc&W3Kx zoR*zz@407Fy=9&u#`hj9s9ISucYft2&$|vboqKRWr~8i2w@kTWTWQyt7SDEHU$}Mp zvv~<8dpz)r|LMCc38#A8TeS7kcUSs)#@BvR(d~ZosJmxEbywZ}>6_kTu9)9DbMvRG z<85Cx4_`egx>52mbJxfTDKYfFd_wj#yX4E}d*UX1wIgGG#Ji^M+imAR@8y)c{NUd& zuKx0JT5e~r0plJ7Dc-tnyBn0XP}MNX%DrN2&CB1mNwElin7H@q?Q$x+>~TA~^v(5d zvuC&KvmF_khYa`h|HHM7IY?T)7`I1u!Reicj>;f-=iQMhHEQSPJh+^7-0VO<+n15U zuBYg}9%{HJJY#mOpv`>nkbx%KIxZ;o$Be$ue*tLa@McOQOprq_w43C+UxjCXqa$n*D|-DBeqR_^?` z#gKr^4zhK$rrSW5Is=w*B zYgTAzi@q0ZR~=rtrMI{L#ie~OH2QPXng(}9{<*dP4>>Bu(}r`F?=nakQ>OQ=%3~@W zh)tS)@|&}-2M)2h+Wcuhi!;5?9E*Q7Hz#qxi{IvlydKlz-G+N*uD0wK)3{f;h^qrL z7scO|PxO0dW#ARxjz&D#w8x02vcsD@^)HP{SWX zfBo}zPSfXk)j0J`an8$PqIL8A)?N2{?p<7IQp4U)%H;~Lbic`NU|{*jktXqj8V%{y zXyP|d_dfhNXW)yUUq#%0uJe=nbr-=;Ry zndw++cfGCUUcUYzjm-=@yis{+$UdiGtEc_$H~jUY7WK#1x%gXynj4zmDEouKl~#?< zpFC)8b2@SDz0e=`^zGgJ(S^B#ZZ29pNpZrro8BKO&QmrfAC*TAwybnM*ZMyBoXS{Bm7Y#n0w9AB$ztrZwsW zmpbvFQgYp&f7qF`GxkmDx82Hn z`SjcMTwnZZK7V7?N=+U2e6=7qR&amYe?s{7zpVBeJXn1!O{j%wjWZY3cRcdI zW%jZm_1gW=%w&V<);(5}ZNBm7@!imcy*fl(Jl;xSn*Cdaa*>|b9ZQYdbaTGd@q?$| zv=81>w#Td$fyWX4(aJl7tq)ETf5I3C6f#^8e$Nx==~ zKe*YAS!QD;o;EiPu$^dtOS}d-jG5)KzE~HewL$vvHbH^caozFP1gmx~SU$AJBBC42 zw4~@k%XZ#zkulMUQ8-vq3JZ63SUqea$JvLdA^pP>Vxr^vVe7Iw!u!#hA40*}8@^g_ zA}!@A9ECAp)v;Phr$9mtDgaJJW z(*)lz^=Y9pFe7(}EO$lrDgTgmu0h;SJ&v!>zau#&6@DaD6AQ7}_+%sCvKPA$;%oGy zxbM;YyO3FPAL8K#@-3+hSJ6azC-^mB!+9pL{al+)Fu|{So10QDGTcD@<8*qgslbug z>m?jn3gRd~1#z^&ttXu~P=}>76~xh^bU~b0=;}pLby$*!1^p3s3gX0H70Dl#%B-M2 zT5KQhv-2+Y`S_1U&cxQ#CU>Ec~aBhEQ<{v{t&N|Jje zP$M72M|1uqA5?ENeeA}zIdL@)N8t!dd@YP6&KAZJZwq6IyM?jD-@;hpaA7R*xGwo6Ha1{m9Kz zxEa3a^b17}WyYzHX@NkkSHRY(Iol6YnJmKg0>uibBji%14`Gy+Q#my~5E0cwE}P_8 zJz?D_7xPErMutKSMLH=pDaf##b*1LdQUn*~!uAND$ncY;kSDI7NZcyTc~&s*d1>a=vU64Rm&j7mj#Q9Z;_3gT!7SQnXyDbi7=D35b=4l|W`)pF4puMR23zYs}j zk6y$WQhPOB33$(oDm;Em)Tc6iDBMkT{N(2$m$s&yYg>e}tgMt696F)AG1iPZF?1nK z(PxOAPaC-rSQiZ@CE**7b05Jt06hfN)&iZ?RCF>P@L`02L_m-IiRh*45f52gh&Fka z4;VPoK+K{pvIy^Nv0$8K0(@E;`Q8U8&;Cdw@dCltMWsp=sKJg9rm;tC6#S{!czl^ME zaYOl)7JbBy@vF`%_(7$q{D$pwVyw!#C?-Qz1H09_>?H({Z1AZiOb0v3 z1%ABhVD~4y=03!sKavufxiBo03!*CJB5FiQb3r+RltfDU%c`(!m;a^xjb8BvyT zqnOUs67z&t{h7U$^M?Koo7#??zM_n;-|awg#)7~f=oIwdcM6!BxS|QWp;Pcf^OnTD zA*+S$e-c)t9F`cWO#{!G4*4qvan%#2i^)^oYxYa>!1#|J5yvGKEJW}I!pt{{B?K^D1C`KEHJSEb@>&+ zf_2yw1xPVv)y*qFS!UlmrE4f!{W!h-u($VKKmN1=G^NR~FJ{xt`M0QItcr#rDjL!- zjpw+@g*s>wh=pBvYsEM_ze@{QS+<4O5H}uD{8WRO(5cO3t-xyIPcF|y*&49MaNi~j zK!u|TfsCCve1FKR9mmQ{7|B*LzZJ$53C+w3 z*`2pEV29zynk%AP;qNvfl}Ahhr?`lzoolWx;J7;%;F?iVM|Lv%&4c?H#m7f8dpb0v zlaHDmGB2iqI-(DyL32r?xHK%f_BiYA%aWu)lbRjQdmExKSYT9mc#<7)y6A-v>N)dN zJWg}{w-!Ns1gaxqTJjb_^wo?m9-^=hYI%G_3!MZK z(Ih3%(L`<=3|$n+Fchc6g!p?)1jwJ1I86GXuBz>n)c>xkL&;%{j3^akk~Nbq0?L}1 zs8cCxCc5&gHsp!-R>TScQdQhuAs@0)hCcwC?Irk4^AHaKF zkeQSi*62N+D2dl6NAq%$XwzDtrgBz?6=Tf%7KfL7P$5go0R4nSUve*~ixt84M447t zGp~WW={>RRLKlwduO1eXobl7GE;xB>hgA+G);2;>@c%%)`;)GZT8}V*^rSUFs8mA` zW=sK)15YWEs;VG#g>AZ=a?P-~qEhvTPI-{{;0AjJpe{>)eErQm#f`@|8z@=^_wPSM z(KdXLqFZ=yLIcIUMRy>a_JS&*Qz}rJ08S(iqh&WJ8V}H_D=AUe;asAoEY%(2BJni$0QpjH z8Hz@Z*Q^_A-g8%U2Ur~Jr;bS-;FC2m{S+)!4P-V0i0RFHorLg|7C9!tBgi6(qdr*C zr6N%)?vVG1`&jbm{vDtB?FZjZQ$XlM>j-o!jm|spWM52lRVo|!9}92l`*$#rZwg4d zXd>IEDa!E2<*#N@sOf9IKY|PQpR8z5EGAt1mV^Lixhzucu_Fe}(YW;dx$n?a!KLh~ zpj91EQucL%-}CCC2}JyKo-_%f=5rLT3JTWhxJw#;IQ*Y16uNJ#u z$N+ef;?M72tbQfwf2oK2@2WaTx{L+35xFkl&4>4{^$+1S-89Xb;q%PogGN^WKv4%PF@mHF5g)A9k+Jo~c6PYJ%cMKJ zX8q9R8BOpAP84JlltYzDrM~A-OGkc?Y_FOUuyRTxF0LFd$S~|-7CVRc3+0AlG?pNywvVljJnGvcmUGRCIWr{Oae>>{0Nu= zm;yTU;$tuU=d(3URUJ;1|FOz)HX> zz-qu6z*+!gDO(TN0N4oF1o#!O8L$Pg6|fDk9e_Pu_8VX)U>9IFU=JV-K>7V0`aZyZ zz#jmb$Q%S50vrbX2_Sz*p{E0m0geMs08Ro<0saEe@c#_pZ@^i=Ily_q1;9nXCBS6> z(W2g#^lNJU2K1YNTY%euJAk`@dw}}@^7|0_Bfw*|Jqvm^;0fR<;2GdK-~}KD@DlI} z@EY(2Ks4T}br`vVRYPgzzgGThRELrq|DpW1|NlY$A1?ntDF5^3zYZlYZ4I9^|CMA3 z=^9SO8oz~$H2(}zzW^J=+F=PWfVF1cxGvtY*suj1F#pdtJ1Xd2l>H|l?-+?LbciaU zC{HEC(aPbc=6^3}cb16}Xs*8~OBko7H>|&K{?`Dqr2Ibuzdy@GDzx^c+5c17i~$tY z|5M*j{XX^olL6G9Q2$T5R{c9Ufa23o+WueL|7%o-k{kbF|L^dx?f;Jk521xSRb|Li zIv#gJF1+WOfV~D8M&Go(<(qHS(Gk6z&p%jou)*phJ?mtHwM!fHmrW3Z*nj$>7j?&p zFFRm7(VzO`F955aBF3M!0+#yOKs82uf4!%=7jA@5gz?7OrY-YgZSZFU46V=)z&2bA zql2}p2 z0jY{ax`Oc}9;GoFDWiu~dx95~LUK<8L?ERK@PalS2_Ft00r2m`x#W$s5f{B#S5Wo_ zCv3Ud8!;6{$D0l2G>AtOlFEmuhu|_%I9w>MHi##Lr;HA3YpR_^))I$*v^Hgf6>hPX z7XZP!@(;D_$N#fHeKU6&nSEnOd-f~08;ObJh-DKB*C4Y z8v=MlO1q&ur&mDfv%If7P!MzTwQlmoIQE^V)2bbia%BZXbeop=jmPhqH2IYI28a+NbRAh($}m-pHu$W z2ZyW>|8UX@qeDHJfO}6kSO(pU@AiwmGkwLK^WsJv;z+PKS32fF7dfQ;Tg|e=5v!BB z*i*{?s|e-6^}-wSDph&h-OBG%rJ_ehxuWsyHXTZeIfnB^Z}JggNBrgO^xLBlR4NNu z@nKXd+D9d2PEpk4QJA2F^7iqFtWyzWtI6{FQ>kc4o|Lj^Quet0poAzaoI%V?a&J zXV-N5Qf8_tRK#D3-hY$Mpd}S$eoaocp0a8C{a-e|*>UZxu6{Gxwa;2~-(rM>-`}aH+t9mL zJ=b^sY1^_tmETKv(y3CW(ub85%Hi(MgjzT`E%&B_DXI09Oh?|?!#(G^61XnWadwR+ znW>Eq_Ge(!k^2XSZrZ5C(OURxw8los4CL(+;`@e&;!tNLCDW59CWXbvgir0GWJcsi z?YMgnxdlW-M2Ch~7@%bOavYJN6EY!Xl*|wbQt1dR$a`RL%m6Iwo1a%Qagdbi4Zv;Dn1v%`Iu4dl`r!{sU?K5k2-lc>6g34&P= z27@ESpZuznjFtvO(Op)i19Jt`LF0TrEg;;slMxfpC{Vyn?!^Tt^=TZ`s^<=WihOB>iy_Py`B<>yG!0iXkK=mF1nS^rG&EBBxa3<+t}MGQOj&RoAm6`sx1aRT>^V1 z4#AH;gvUDA_tXiu$LJ;|#3rDPH-u=g9B6kRnwj`d-}C~wp^ujtUx-sss=S@dZZ zI?{VyYME)N!U%6>@8#p{rF6uQ#>vCSy`h~l)y|`#hnKfgs`qJk_f${ER9*YA-l;QL zWI}MP!Y3N3PVDI!9~&Dli<9g5N8>;j2A~m1iomE~j61@52Ke~2!XVSmuBU%sfUVN2 zO&go<%trYu?A+{Zm5P9n5qlM>ZK6XGf)j=?KW`?K9(h`ql^dxOXm+wP(@!-*gGi+o z;fo(w&@02%-~^d$9UiT&U}$D7WN1RayEhhX=6f9jJh$9u>G-3A_29Hv$GFfgj` zAyoeTYib%ys_AunnYo)@N4LaiW&mueA^|70R)(Yr@o7SpW5)IujQ==ZqvnYkeY34S zZ2qXRcke9Tj+lr0f^ra<7Goxyws1(daQNvk*KS%IE`QHdW)@Pv0M?UkK zzcDl7>1$OP+|C6#nkC)OM!a_s_q51S2RE6> zr=$p{9vc**Qb?;p?>2LD!`9RdPjfCM9GgpYS4TQpCw1mc<}4JkAVRDG-HtfV2Hh-C@)A{cz5;mZe#1+)thokT;;tn zC8?W8TU~!dhL2F=Ua56AF=F+XlB6ua9@J}@72MS#RFa@vfQsVT-WD>HVy7|?QLv>@ z5~9`Bg1DlNk^QK~NeA%S6-q*z6E7RS?qOlJ>lSu?y{>dXAV)DdvjZ@>wsB35+?nC;(kIWbrjd2A~rY1P(DhU7x z+K-(Xm8g+k^!Zh!PAwDiclQ)RlsGh;$gh$NxW6 z`V7LV!lxE`~;2mUn;|YO#r2%BtfsklcTyl=wjwN0J)~s z6j~E%g%yhktXm`$yS|b?hB}C!NYw$9H2w$hOJ@^EsU>ZJd3=5WTkQN@lDYY2-)f}2 z2GSl8ap~-!ffV-}TX{9N9@^N&(aWikmwiJ=Pak~jJusNEcXw&%;N;WT-pq;OQ{KP^=jwQ+}gXbx0BMov14OzXHN$&XD<=p>g(Cu+TF>$v7^$#-cjje=V(Wu z^oEnEQID4K7~*;lP703;3r|qAii?QvWoq=ED|XF?IygJndo^-YHuUsyQ8u)*^Ym%x z?Ct2#P^olRdb-%zDV0v{!<(Y3Yx+Kvl2fMPVIq!OhJ&EG6q?&RHc${a+c`H-G;(xm zpfEMkERW{O28#Fpp19M%FYy(LwJ#FuP!P*2uKM#1El#(HVF~rQ8Q;!-@+u(8Y}Jnj ztbXlHn5~ItifDvE?rs1Q_Yl}zeD{Q27SJC+gN(5NQ@}JpIluw{wKW>SSwbg28kbRv z#i0XZc|ADqpn2S>Klh*FfZru`gF72HivLEb*b5P&WU9FQQ#xg5($CDXwJ@7H1Nm?8 zYONP=x&?5$Sk;Wj>NLrqaLAsdPn1W_pCNxUkq$?7Mg^=3(^_(W^-6ika-HYvbAEp| z7r{TZpUI1{u<29#pTZRW8RnnFKNbza3qYd^Y7^}Mr2$<5l>kuy8u3t@pn6P89n=<5 z0TlsK8<`9XwLz29tz8`6Mgj(cKpr z3OjzUQ2eb1p4z>)o-A6l2zW|H?L2?At4qJXz@J0Q=knz41Kz^(Ays$Y+-D|;lVkz4 zYl@dkI+9e0JlT|Vaw2g+@;^+`0YCB(p69q4`KC0Jat07DM+1l#697aY21tryG;|tX z(sMavCqKlypmwdif?Bp|)z&YN`zKsjcw%TmbpIrDCEPES)3MCE*7}tfl~Mlt)*>FsgDS`UD&?&?4Z;;iL-Q6o zjU|b;CXFceRlcxr&X*42d*Lhe;;2-U78L~5#o)O3q$u=owD1=WyM(`Q1} zJC7dUQsXI)Po8EyxC>oV2{^r?gW)!{Aw_}dSao>JliT9F0sSggI~DJ%2WO&3bVP(R ZzbwaHZFv`G>&%|HZ2Vd?vj0Kz{{vwg_2&Qp literal 0 HcmV?d00001 diff --git a/src/testcases/org/apache/poi/poifs/data/ppt_with_embeded.xls b/src/testcases/org/apache/poi/poifs/data/ppt_with_embeded.xls new file mode 100644 index 0000000000000000000000000000000000000000..72e7232cd100a44006f7c2c45921468f0b3979e4 GIT binary patch literal 79360 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
    )B*4wAla_6c=zvk0&l!}H!@HTgVw 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 NwEJ%(5&tEO{4XD`cFzC+ literal 0 HcmV?d00001 diff --git a/src/testcases/org/apache/poi/poifs/data/word_with_embeded.doc b/src/testcases/org/apache/poi/poifs/data/word_with_embeded.doc new file mode 100644 index 0000000000000000000000000000000000000000..d9fea29c24438ba32e04a1360fd528032594f399 GIT binary patch literal 75776 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 literal 0 HcmV?d00001 diff --git a/src/testcases/org/apache/poi/poifs/filesystem/AllPOIFSFileSystemTests.java b/src/testcases/org/apache/poi/poifs/filesystem/AllPOIFSFileSystemTests.java new file mode 100755 index 000000000..2a0319afe --- /dev/null +++ b/src/testcases/org/apache/poi/poifs/filesystem/AllPOIFSFileSystemTests.java @@ -0,0 +1,44 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.poifs.filesystem; + +import junit.framework.Test; +import junit.framework.TestSuite; + +/** + * Tests for org.apache.poi.poifs.filesystem
    + * + * @author Josh Micich + */ +public final class AllPOIFSFileSystemTests { + + public static Test suite() { + TestSuite result = new TestSuite("Tests for org.apache.poi.poifs.filesystem"); + result.addTestSuite(TestDirectoryNode.class); + result.addTestSuite(TestDocument.class); + result.addTestSuite(TestDocumentDescriptor.class); + result.addTestSuite(TestDocumentInputStream.class); + result.addTestSuite(TestDocumentNode.class); + result.addTestSuite(TestDocumentOutputStream.class); + result.addTestSuite(TestEmptyDocument.class); + result.addTestSuite(TestOffice2007XMLException.class); + result.addTestSuite(TestPOIFSDocumentPath.class); + result.addTestSuite(TestPropertySorter.class); + return result; + } +} diff --git a/src/testcases/org/apache/poi/poifs/filesystem/TestEmptyDocument.java b/src/testcases/org/apache/poi/poifs/filesystem/TestEmptyDocument.java index 4f67f9876..870d75252 100644 --- a/src/testcases/org/apache/poi/poifs/filesystem/TestEmptyDocument.java +++ b/src/testcases/org/apache/poi/poifs/filesystem/TestEmptyDocument.java @@ -20,6 +20,7 @@ package org.apache.poi.poifs.filesystem; import java.io.IOException; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.util.Arrays; import junit.framework.TestCase; @@ -157,12 +158,15 @@ public class TestEmptyDocument extends TestCase { DocumentEntry entry = (DocumentEntry) fs.getRoot().getEntry("Empty"); assertEquals("Expected zero size", 0, entry.getSize()); + byte[] actualReadbackData; + actualReadbackData = IOUtils.toByteArray(new DocumentInputStream(entry)); assertEquals("Expected zero read from stream", 0, - IOUtils.toByteArray(new DocumentInputStream(entry)).length); + actualReadbackData.length); entry = (DocumentEntry) fs.getRoot().getEntry("NotEmpty"); + actualReadbackData = IOUtils.toByteArray(new DocumentInputStream(entry)); assertEquals("Expected size was wrong", testData.length, entry.getSize()); - assertEquals("Expected different data read from stream", testData, - IOUtils.toByteArray(new DocumentInputStream(entry))); + assertTrue("Expected different data read from stream", + Arrays.equals(testData, actualReadbackData)); } } diff --git a/src/testcases/org/apache/poi/poifs/filesystem/TestPOIFSFileSystem.java b/src/testcases/org/apache/poi/poifs/filesystem/TestPOIFSFileSystem.java new file mode 100755 index 000000000..dbc5401be --- /dev/null +++ b/src/testcases/org/apache/poi/poifs/filesystem/TestPOIFSFileSystem.java @@ -0,0 +1,136 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.poifs.filesystem; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; + +import junit.framework.TestCase; + +/** + * Tests for POIFSFileSystem + * + * @author Josh Micich + */ +public final class TestPOIFSFileSystem extends TestCase { + + public TestPOIFSFileSystem(String testName) { + super(testName); + } + + /** + * Mock exception used to ensure correct error handling + */ + private static final class MyEx extends RuntimeException { + public MyEx() { + // no fields to initialise + } + } + /** + * Helps facilitate testing. Keeps track of whether close() was called. + * Also can throw an exception at a specific point in the stream. + */ + private static final class TestIS extends InputStream { + + private final InputStream _is; + private final int _failIndex; + private int _currentIx; + private boolean _isClosed; + + public TestIS(InputStream is, int failIndex) { + _is = is; + _failIndex = failIndex; + _currentIx = 0; + _isClosed = false; + } + + public int read() throws IOException { + int result = _is.read(); + if(result >=0) { + checkRead(1); + } + return result; + } + public int read(byte[] b, int off, int len) throws IOException { + int result = _is.read(b, off, len); + checkRead(result); + return result; + } + + private void checkRead(int nBytes) { + _currentIx += nBytes; + if(_failIndex > 0 && _currentIx > _failIndex) { + throw new MyEx(); + } + } + public void close() throws IOException { + _isClosed = true; + _is.close(); + } + public boolean isClosed() { + return _isClosed; + } + } + + /** + * Test for undesired behaviour observable as of svn revision 618865 (5-Feb-2008). + * POIFSFileSystem was not closing the input stream. + */ + public void testAlwaysClose() { + + TestIS testIS; + + // Normal case - read until EOF and close + testIS = new TestIS(openSampleStream("13224.xls"), -1); + try { + new POIFSFileSystem(testIS); + } catch (IOException e) { + throw new RuntimeException(e); + } + assertTrue("input stream was not closed", testIS.isClosed()); + + // intended to crash after reading 10000 bytes + testIS = new TestIS(openSampleStream("13224.xls"), 10000); + try { + new POIFSFileSystem(testIS); + fail("ex should have been thrown"); + } catch (IOException e) { + throw new RuntimeException(e); + } catch (MyEx e) { + // expected + } + assertTrue("input stream was not closed", testIS.isClosed()); // but still should close + + } + + private static InputStream openSampleStream(String sampleName) { + String dataDirName = System.getProperty("HSSF.testdata.path"); + if(dataDirName == null) { + throw new RuntimeException("Missing system property '" + "HSSF.testdata.path" + "'"); + } + File f = new File(dataDirName + "/" + sampleName); + try { + return new FileInputStream(f); + } catch (FileNotFoundException e) { + throw new RuntimeException("Sample file '" + f.getAbsolutePath() + "' not found"); + } + } +} diff --git a/src/testcases/org/apache/poi/poifs/property/AllPOIFSPropertyTests.java b/src/testcases/org/apache/poi/poifs/property/AllPOIFSPropertyTests.java new file mode 100755 index 000000000..a5459ed76 --- /dev/null +++ b/src/testcases/org/apache/poi/poifs/property/AllPOIFSPropertyTests.java @@ -0,0 +1,22 @@ +package org.apache.poi.poifs.property; + +import junit.framework.Test; +import junit.framework.TestSuite; + +/** + * Tests for org.apache.poi.poifs.property
    + * + * @author Josh Micich + */ +public final class AllPOIFSPropertyTests { + + public static Test suite() { + TestSuite result = new TestSuite("Tests for org.apache.poi.poifs.property"); + result.addTestSuite(TestDirectoryProperty.class); + result.addTestSuite(TestDocumentProperty.class); + result.addTestSuite(TestPropertyFactory.class); + result.addTestSuite(TestPropertyTable.class); + result.addTestSuite(TestRootProperty.class); + return result; + } +} diff --git a/src/testcases/org/apache/poi/poifs/storage/AllPOIFSStorageTests.java b/src/testcases/org/apache/poi/poifs/storage/AllPOIFSStorageTests.java new file mode 100755 index 000000000..8c15d389e --- /dev/null +++ b/src/testcases/org/apache/poi/poifs/storage/AllPOIFSStorageTests.java @@ -0,0 +1,47 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.poifs.storage; + +import junit.framework.Test; +import junit.framework.TestSuite; +/** + * Tests for org.apache.poi.poifs.storage
    + * + * @author Josh Micich + */ +public final class AllPOIFSStorageTests { + + public static Test suite() { + TestSuite result = new TestSuite("Tests for org.apache.poi.poifs.storage"); + result.addTestSuite(TestBATBlock.class); + result.addTestSuite(TestBlockAllocationTableReader.class); + result.addTestSuite(TestBlockAllocationTableWriter.class); + result.addTestSuite(TestBlockListImpl.class); + result.addTestSuite(TestDocumentBlock.class); + result.addTestSuite(TestHeaderBlockReader.class); + result.addTestSuite(TestHeaderBlockWriter.class); + result.addTestSuite(TestPropertyBlock.class); + result.addTestSuite(TestRawDataBlock.class); + result.addTestSuite(TestRawDataBlockList.class); + result.addTestSuite(TestSmallBlockTableReader.class); + result.addTestSuite(TestSmallBlockTableWriter.class); + result.addTestSuite(TestSmallDocumentBlock.class); + result.addTestSuite(TestSmallDocumentBlockList.class); + return result; + } +} diff --git a/src/testcases/org/apache/poi/poifs/storage/TestRawDataBlock.java b/src/testcases/org/apache/poi/poifs/storage/TestRawDataBlock.java index 1473fa82e..4c84f04b7 100644 --- a/src/testcases/org/apache/poi/poifs/storage/TestRawDataBlock.java +++ b/src/testcases/org/apache/poi/poifs/storage/TestRawDataBlock.java @@ -22,6 +22,9 @@ package org.apache.poi.poifs.storage; import java.io.*; import java.util.Random; +import org.apache.poi.util.DummyPOILogger; +import org.apache.poi.util.POILogFactory; + import junit.framework.*; /** @@ -43,6 +46,13 @@ public class TestRawDataBlock public TestRawDataBlock(String name) { super(name); + + // We always want to use our own + // logger + System.setProperty( + "org.apache.poi.util.POILogger", + "org.apache.poi.util.DummyPOILogger" + ); } /** @@ -99,11 +109,19 @@ public class TestRawDataBlock /** * Test creating a short RawDataBlock + * Will trigger a warning, but no longer an IOException, + * as people seem to have "valid" truncated files */ - - public void testShortConstructor() + public void testShortConstructor() throws Exception { - for (int k = 1; k < 512; k++) + // Get the logger to be used + DummyPOILogger logger = (DummyPOILogger)POILogFactory.getLogger( + RawDataBlock.class + ); + assertEquals(0, logger.logged.size()); + + // Test for various data sizes + for (int k = 1; k <= 512; k++) { byte[] data = new byte[ k ]; @@ -112,16 +130,33 @@ public class TestRawDataBlock data[ j ] = ( byte ) j; } RawDataBlock block = null; - - try - { - block = new RawDataBlock(new ByteArrayInputStream(data)); - fail("Should have thrown IOException creating short block"); - } - catch (IOException ignored) - { - - // as expected + + logger.reset(); + assertEquals(0, logger.logged.size()); + + // Have it created + block = new RawDataBlock(new ByteArrayInputStream(data)); + assertNotNull(block); + + // Check for the warning is there for <512 + if(k < 512) { + assertEquals( + "Warning on " + k + " byte short block", + 1, logger.logged.size() + ); + + // Build the expected warning message, and check + String bts = k + " byte"; + if(k > 1) { + bts += "s"; + } + + assertEquals( + "7 - Unable to read entire block; "+bts+" read before EOF; expected 512 bytes. Your document has probably been truncated!", + (String)(logger.logged.get(0)) + ); + } else { + assertEquals(0, logger.logged.size()); } } } @@ -132,6 +167,13 @@ public class TestRawDataBlock * incorrectly think that there's not enough data */ public void testSlowInputStream() throws Exception { + // Get the logger to be used + DummyPOILogger logger = (DummyPOILogger)POILogFactory.getLogger( + RawDataBlock.class + ); + assertEquals(0, logger.logged.size()); + + // Test for various ok data sizes for (int k = 1; k < 512; k++) { byte[] data = new byte[ 512 ]; for (int j = 0; j < data.length; j++) { @@ -153,14 +195,17 @@ public class TestRawDataBlock data[j] = (byte) j; } - // Shouldn't complain, as there is enough data - try { - RawDataBlock block = - new RawDataBlock(new SlowInputStream(data, k)); - fail(); - } catch(IOException e) { - // as expected - } + logger.reset(); + assertEquals(0, logger.logged.size()); + + // Should complain, as there isn't enough data + RawDataBlock block = + new RawDataBlock(new SlowInputStream(data, k)); + assertNotNull(block); + assertEquals( + "Warning on " + k + " byte short block", + 1, logger.logged.size() + ); } } diff --git a/src/testcases/org/apache/poi/poifs/storage/TestRawDataBlockList.java b/src/testcases/org/apache/poi/poifs/storage/TestRawDataBlockList.java index ee63825e2..ac6fc08c0 100644 --- a/src/testcases/org/apache/poi/poifs/storage/TestRawDataBlockList.java +++ b/src/testcases/org/apache/poi/poifs/storage/TestRawDataBlockList.java @@ -21,6 +21,9 @@ package org.apache.poi.poifs.storage; import java.io.*; +import org.apache.poi.util.DummyPOILogger; +import org.apache.poi.util.POILogFactory; + import junit.framework.*; /** @@ -42,6 +45,13 @@ public class TestRawDataBlockList public TestRawDataBlockList(String name) { super(name); + + // We always want to use our own + // logger + System.setProperty( + "org.apache.poi.util.POILogger", + "org.apache.poi.util.DummyPOILogger" + ); } /** @@ -78,8 +88,15 @@ public class TestRawDataBlockList * Test creating a short RawDataBlockList */ - public void testShortConstructor() + public void testShortConstructor() throws Exception { + // Get the logger to be used + DummyPOILogger logger = (DummyPOILogger)POILogFactory.getLogger( + RawDataBlock.class + ); + assertEquals(0, logger.logged.size()); + + // Test for various short sizes for (int k = 2049; k < 2560; k++) { byte[] data = new byte[ k ]; @@ -88,16 +105,11 @@ public class TestRawDataBlockList { data[ j ] = ( byte ) j; } - try - { - new RawDataBlockList(new ByteArrayInputStream(data)); - fail("Should have thrown IOException creating short block"); - } - catch (IOException ignored) - { - // as expected - } + // Check we logged the error + logger.reset(); + new RawDataBlockList(new ByteArrayInputStream(data)); + assertEquals(1, logger.logged.size()); } } diff --git a/src/testcases/org/apache/poi/util/AllPOIUtilTests.java b/src/testcases/org/apache/poi/util/AllPOIUtilTests.java new file mode 100755 index 000000000..bb6d38204 --- /dev/null +++ b/src/testcases/org/apache/poi/util/AllPOIUtilTests.java @@ -0,0 +1,51 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.util; + +import junit.framework.Test; +import junit.framework.TestSuite; +/** + * Test suite for all sub-packages of org.apache.poi.util
    + * + * @author Josh Micich + */ +public final class AllPOIUtilTests { + + public static Test suite() { + TestSuite result = new TestSuite("Tests for org.apache.poi.util"); + result.addTestSuite(TestArrayUtil.class); + result.addTestSuite(TestBinaryTree.class); + result.addTestSuite(TestBitField.class); + result.addTestSuite(TestByteField.class); + result.addTestSuite(TestDoubleList2d.class); + result.addTestSuite(TestHexDump.class); + result.addTestSuite(TestIntegerField.class); + result.addTestSuite(TestIntList.class); + result.addTestSuite(TestIntList2d.class); + result.addTestSuite(TestList2d.class); + result.addTestSuite(TestLittleEndian.class); + result.addTestSuite(TestLongField.class); + result.addTestSuite(TestPOILogFactory.class); + result.addTestSuite(TestPOILogger.class); + result.addTestSuite(TestShortField.class); + result.addTestSuite(TestShortList.class); + result.addTestSuite(TestStringUtil.class); + result.addTestSuite(TestTempFile.class); + return result; + } +} diff --git a/src/testcases/org/apache/poi/util/DummyPOILogger.java b/src/testcases/org/apache/poi/util/DummyPOILogger.java new file mode 100644 index 000000000..7efbfac29 --- /dev/null +++ b/src/testcases/org/apache/poi/util/DummyPOILogger.java @@ -0,0 +1,46 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ +package org.apache.poi.util; + +import java.util.ArrayList; +import java.util.List; + +/** + * POILogger which logs into an ArrayList, so that + * tests can see what got logged + */ +public class DummyPOILogger extends POILogger { + public List logged = new ArrayList(); + + public void reset() { + logged = new ArrayList(); + } + + public boolean check(int level) { + return true; + } + + public void initialize(String cat) {} + + public void log(int level, Object obj1) { + logged.add(new String(level + " - " + obj1)); + } + + public void log(int level, Object obj1, Throwable exception) { + logged.add(new String(level + " - " + obj1 + " - " + exception)); + } +}