]> source.dussan.org Git - poi.git/commitdiff
Merged revisions 627779-634630 via svnmerge from
authorUgo Cei <ugo@apache.org>
Sat, 8 Mar 2008 11:49:00 +0000 (11:49 +0000)
committerUgo Cei <ugo@apache.org>
Sat, 8 Mar 2008 11:49:00 +0000 (11:49 +0000)
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

206 files changed:
build.xml
legal/LICENSE
legal/NOTICE
src/contrib/src/org/apache/poi/hssf/contrib/view/SVRowHeader.java
src/contrib/src/org/apache/poi/hssf/contrib/view/SVTableCellEditor.java
src/contrib/src/org/apache/poi/hssf/contrib/view/SVTableCellRenderer.java
src/contrib/src/org/apache/poi/hssf/contrib/view/SViewer.java
src/contrib/src/org/apache/poi/hssf/contrib/view/SViewerPanel.java
src/documentation/content/xdocs/changes.xml
src/documentation/content/xdocs/getinvolved/index.xml
src/documentation/content/xdocs/hslf/index.xml
src/documentation/content/xdocs/status.xml
src/java/org/apache/poi/POIDocument.java
src/java/org/apache/poi/hssf/model/FormulaParser.java
src/java/org/apache/poi/hssf/model/LinkTable.java [new file with mode: 0755]
src/java/org/apache/poi/hssf/model/RecordStream.java [new file with mode: 0755]
src/java/org/apache/poi/hssf/model/Workbook.java
src/java/org/apache/poi/hssf/record/CRNCountRecord.java [new file with mode: 0755]
src/java/org/apache/poi/hssf/record/CRNRecord.java [new file with mode: 0755]
src/java/org/apache/poi/hssf/record/DVALRecord.java
src/java/org/apache/poi/hssf/record/ExternalNameRecord.java [new file with mode: 0755]
src/java/org/apache/poi/hssf/record/FileSharingRecord.java
src/java/org/apache/poi/hssf/record/NameRecord.java
src/java/org/apache/poi/hssf/record/RecordFactory.java
src/java/org/apache/poi/hssf/record/RecordInputStream.java
src/java/org/apache/poi/hssf/record/SharedFormulaRecord.java
src/java/org/apache/poi/hssf/record/StyleRecord.java
src/java/org/apache/poi/hssf/record/SupBookRecord.java
src/java/org/apache/poi/hssf/record/aggregates/ValueRecordsAggregate.java
src/java/org/apache/poi/hssf/record/constant/ConstantValueParser.java [new file with mode: 0755]
src/java/org/apache/poi/hssf/record/formula/AbstractFunctionPtg.java
src/java/org/apache/poi/hssf/record/formula/Area3DPtg.java
src/java/org/apache/poi/hssf/record/formula/AreaAPtg.java
src/java/org/apache/poi/hssf/record/formula/AreaI.java [new file with mode: 0644]
src/java/org/apache/poi/hssf/record/formula/AreaPtg.java
src/java/org/apache/poi/hssf/record/formula/AreaVPtg.java
src/java/org/apache/poi/hssf/record/formula/ArrayPtg.java
src/java/org/apache/poi/hssf/record/formula/ErrPtg.java
src/java/org/apache/poi/hssf/record/formula/FuncPtg.java
src/java/org/apache/poi/hssf/record/formula/IntPtg.java
src/java/org/apache/poi/hssf/record/formula/NamePtg.java
src/java/org/apache/poi/hssf/record/formula/NameXPtg.java
src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java
src/java/org/apache/poi/hssf/record/formula/RefAPtg.java
src/java/org/apache/poi/hssf/record/formula/RefVPtg.java
src/java/org/apache/poi/hssf/record/formula/ReferencePtg.java
src/java/org/apache/poi/hssf/record/formula/SheetNameFormatter.java
src/java/org/apache/poi/hssf/usermodel/HSSFCell.java
src/java/org/apache/poi/hssf/usermodel/HSSFErrorConstants.java
src/java/org/apache/poi/hssf/usermodel/HSSFPalette.java
src/java/org/apache/poi/hssf/usermodel/HSSFRow.java
src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java
src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java
src/java/org/apache/poi/hssf/util/AreaReference.java
src/java/org/apache/poi/hssf/util/CellReference.java
src/java/org/apache/poi/poifs/filesystem/POIFSFileSystem.java
src/java/org/apache/poi/poifs/storage/RawDataBlock.java
src/java/org/apache/poi/util/LittleEndian.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/AddEval.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/Area2DEval.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/Area3DEval.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/AreaEval.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/BoolEval.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ConcatEval.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/DivideEval.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ErrorEval.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/EvaluationException.java [new file with mode: 0755]
src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ExternalFunction.java [new file with mode: 0755]
src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/FunctionEval.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/MultiplyEval.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/NameEval.java [new file with mode: 0755]
src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/OperandResolver.java [new file with mode: 0755]
src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/PowerEval.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/Ref2DEval.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/Ref3DEval.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/RefEval.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/StringEval.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/StringValueEval.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/SubtractEval.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/UnaryMinusEval.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/UnaryPlusEval.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ValueEvalToNumericXlator.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Avedev.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Average.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/FinanceFunction.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/FreeRefFunction.java [new file with mode: 0755]
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Hlookup.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/If.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Indirect.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Isblank.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Len.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Lookup.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/LookupUtils.java [new file with mode: 0644]
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Match.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Maxa.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Mid.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Mina.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/MultiOperandNumericFunction.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Na.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/NumericFunction.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Offset.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Rounddown.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Roundup.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Rows.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Stdev.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumproduct.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumsq.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumx2my2.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumx2py2.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumxmy2.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/T.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Trim.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Vlookup.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/XYNumericFunction.java
src/scratchpad/src/org/apache/poi/hssf/usermodel/EvaluationCycleDetector.java [new file with mode: 0755]
src/scratchpad/src/org/apache/poi/hssf/usermodel/EvaluationCycleDetectorManager.java [new file with mode: 0755]
src/scratchpad/src/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java
src/scratchpad/testcases/org/apache/poi/hdgf/data/ShortChunk1.vsd [new file with mode: 0755]
src/scratchpad/testcases/org/apache/poi/hdgf/data/ShortChunk2.vsd [new file with mode: 0755]
src/scratchpad/testcases/org/apache/poi/hdgf/data/ShortChunk3.vsd [new file with mode: 0755]
src/scratchpad/testcases/org/apache/poi/hssf/data/42464-ExpPtg-bad.xls [deleted file]
src/scratchpad/testcases/org/apache/poi/hssf/data/42464-ExpPtg-ok.xls [deleted file]
src/scratchpad/testcases/org/apache/poi/hssf/data/44297.xls [deleted file]
src/scratchpad/testcases/org/apache/poi/hssf/data/FormulaEvalTestData.xls [deleted file]
src/scratchpad/testcases/org/apache/poi/hssf/data/MissingBits.xls [deleted file]
src/scratchpad/testcases/org/apache/poi/hssf/eventusermodel/TestMissingRecordAwareHSSFListener.java
src/scratchpad/testcases/org/apache/poi/hssf/model/TestFormulaParserSP.java
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/AllFormulaEvalTests.java [new file with mode: 0755]
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/GenericFormulaTestCase.java [deleted file]
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestCircularReferences.java [new file with mode: 0755]
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestEverything.java [deleted file]
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestExternalFunction.java [new file with mode: 0755]
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestFormulasFromSpreadsheet.java [new file with mode: 0644]
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestUnaryPlusEval.java [new file with mode: 0755]
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/EvalFactory.java
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/NumericFunctionInvoker.java
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestAverage.java [new file with mode: 0755]
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestCountFuncs.java
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestEverything.java [deleted file]
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestIsBlank.java [new file with mode: 0755]
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestLen.java [new file with mode: 0755]
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestLookupFunctionsFromSpreadsheet.java [new file with mode: 0644]
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestMatch.java [new file with mode: 0755]
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestMid.java [new file with mode: 0755]
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestOffset.java [new file with mode: 0755]
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestRoundFuncs.java [new file with mode: 0755]
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestSumproduct.java [new file with mode: 0755]
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestTFunc.java [new file with mode: 0755]
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestTrim.java [new file with mode: 0755]
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestXYNumericFunction.java [new file with mode: 0755]
src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestBug42464.java
src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestBug44410.java [new file with mode: 0644]
src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestBug44508.java [new file with mode: 0644]
src/testcases/org/apache/poi/AllPOITests.java [new file with mode: 0755]
src/testcases/org/apache/poi/TestPOIDocumentMain.java
src/testcases/org/apache/poi/ddf/AllPOIDDFTests.java [new file with mode: 0755]
src/testcases/org/apache/poi/hpsf/basic/AllPOIHPSFBasicTests.java [new file with mode: 0755]
src/testcases/org/apache/poi/hssf/HSSFTests.java
src/testcases/org/apache/poi/hssf/data/44297.xls [new file with mode: 0755]
src/testcases/org/apache/poi/hssf/data/AbnormalSharedFormulaFlag.xls [new file with mode: 0755]
src/testcases/org/apache/poi/hssf/data/FormulaEvalTestData.xls [new file with mode: 0644]
src/testcases/org/apache/poi/hssf/data/LookupFunctionsTestCaseData.xls [new file with mode: 0755]
src/testcases/org/apache/poi/hssf/data/MissingBits.xls [new file with mode: 0644]
src/testcases/org/apache/poi/hssf/data/OddStyleRecord.xls [new file with mode: 0644]
src/testcases/org/apache/poi/hssf/data/ReadOnlyRecommended.xls [new file with mode: 0644]
src/testcases/org/apache/poi/hssf/data/SingleLetterRanges.xls [new file with mode: 0644]
src/testcases/org/apache/poi/hssf/data/TestDataValidation.xls [deleted file]
src/testcases/org/apache/poi/hssf/data/externalFunctionExample.xls [new file with mode: 0755]
src/testcases/org/apache/poi/hssf/data/logoKarmokar4.png [new file with mode: 0755]
src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java
src/testcases/org/apache/poi/hssf/record/AllRecordTests.java [new file with mode: 0755]
src/testcases/org/apache/poi/hssf/record/TestDVALRecord.java [new file with mode: 0755]
src/testcases/org/apache/poi/hssf/record/TestSupBookRecord.java
src/testcases/org/apache/poi/hssf/record/TestcaseRecordInputStream.java
src/testcases/org/apache/poi/hssf/record/aggregates/TestValueRecordsAggregate.java
src/testcases/org/apache/poi/hssf/record/formula/AbstractPtgTestCase.java
src/testcases/org/apache/poi/hssf/record/formula/AllFormulaTests.java
src/testcases/org/apache/poi/hssf/record/formula/TestAreaPtg.java
src/testcases/org/apache/poi/hssf/record/formula/TestExternalFunctionFormulas.java [new file with mode: 0755]
src/testcases/org/apache/poi/hssf/usermodel/AllUserModelTests.java [new file with mode: 0755]
src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java
src/testcases/org/apache/poi/hssf/usermodel/TestDataValidation.java
src/testcases/org/apache/poi/hssf/usermodel/TestFormulas.java
src/testcases/org/apache/poi/hssf/usermodel/TestHSSFPalette.java
src/testcases/org/apache/poi/hssf/usermodel/TestHSSFPicture.java
src/testcases/org/apache/poi/hssf/usermodel/TestHSSFRow.java
src/testcases/org/apache/poi/hssf/usermodel/TestNamedRange.java
src/testcases/org/apache/poi/hssf/usermodel/TestPOIFSProperties.java [new file with mode: 0644]
src/testcases/org/apache/poi/hssf/util/AllHSSFUtilTests.java [new file with mode: 0755]
src/testcases/org/apache/poi/hssf/util/TestAreaReference.java
src/testcases/org/apache/poi/hssf/util/TestCellReference.java
src/testcases/org/apache/poi/poifs/AllPOIFSTests.java [new file with mode: 0755]
src/testcases/org/apache/poi/poifs/data/excel_with_embeded.xls [new file with mode: 0644]
src/testcases/org/apache/poi/poifs/data/ppt_with_embeded.xls [new file with mode: 0644]
src/testcases/org/apache/poi/poifs/data/visio_with_embeded.xls [new file with mode: 0644]
src/testcases/org/apache/poi/poifs/data/word_with_embeded.doc [new file with mode: 0644]
src/testcases/org/apache/poi/poifs/filesystem/AllPOIFSFileSystemTests.java [new file with mode: 0755]
src/testcases/org/apache/poi/poifs/filesystem/TestEmptyDocument.java
src/testcases/org/apache/poi/poifs/filesystem/TestPOIFSFileSystem.java [new file with mode: 0755]
src/testcases/org/apache/poi/poifs/property/AllPOIFSPropertyTests.java [new file with mode: 0755]
src/testcases/org/apache/poi/poifs/storage/AllPOIFSStorageTests.java [new file with mode: 0755]
src/testcases/org/apache/poi/poifs/storage/TestRawDataBlock.java
src/testcases/org/apache/poi/poifs/storage/TestRawDataBlockList.java
src/testcases/org/apache/poi/util/AllPOIUtilTests.java [new file with mode: 0755]
src/testcases/org/apache/poi/util/DummyPOILogger.java [new file with mode: 0644]

index 8905dc13f57361016fdb3575ba5bdc879a65751b..3c40743da67c59a5fe0897e73eb18ce16adc9642 100644 (file)
--- a/build.xml
+++ b/build.xml
@@ -557,8 +557,7 @@ under the License.
       <batchtest todir="${main.reports.test}">
         <fileset dir="${main.src.test}">
           <include name="**/Test*.java"/>
-          <exclude name="**/AllTests.java"/>
-          <exclude name="**/TestEmptyDocument.java"/>
+          <exclude name="**/All*Tests.java"/>
           <exclude name="**/TestUnfixedBugs.java"/>
           <exclude name="**/TestcaseRecordInputStream.java"/>
         </fileset>
@@ -663,7 +662,7 @@ under the License.
                 <pathelement location="${scratchpad.output.test.dir}"/>
                 <pathelement location="${junit.jar1.dir}"/>
             </classpath>
-            <sysproperty key="HSSF.testdata.path" file="${scratchpad.src.test}/org/apache/poi/hssf/data"/>
+            <sysproperty key="HSSF.testdata.path" file="${main.src.test}/org/apache/poi/hssf/data"/>
             <sysproperty key="HPSF.testdata.path" file="${scratchpad.src.test}/org/apache/poi/hpsf/data"/>
             <sysproperty key="HDF.testdata.path" file="${scratchpad.src.test}/org/apache/poi/hdf/data"/>
             <sysproperty key="HWPF.testdata.path" file="${scratchpad.src.test}/org/apache/poi/hwpf/data"/>
@@ -698,7 +697,7 @@ under the License.
                                        <pathelement location="${scratchpad.output.test.dir}"/>
                                        <pathelement location="${junit.jar1.dir}"/>
                                    </classpath>
-                   <sysproperty key="HSSF.testdata.path" file="${scratchpad.src.test}/org/apache/poi/hssf/data"/>
+                <sysproperty key="HSSF.testdata.path" file="${main.src.test}/org/apache/poi/hssf/data"/>
                    <sysproperty key="HPSF.testdata.path" file="${scratchpad.src.test}/org/apache/poi/hpsf/data"/>
                    <sysproperty key="HWPF.testdata.path" file="${scratchpad.src.test}/org/apache/poi/hwpf/data"/>
                 <sysproperty key="HSLF.testdata.path" file="${scratchpad.src.test}/org/apache/poi/hslf/data"/>
index d645695673349e3947e8e5ae42332d0ac3164cd7..b547443dea4e80589481a7881096ab313bbdfcc1 100755 (executable)
    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 <http://www.ecma-international.org/publications/standards/Ecma-376.htm>
+
+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/
index 41c966ff2775baf0693cb9a16bb6483d94ab4d49..d5d7883b35d8dbefefa6241eca9cd9fbbe0a7c1c 100644 (file)
@@ -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
index f53d9cd89d5a3535b7655a0666f26883362011bc..fe63dfcc8afaf20f614b5b0ffc2aea65588ce656 100644 (file)
 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());
index a433a7a11c145655fe8258ddb8ea6caea7b751db..8b421ceaf1d7dd33a869ae59e304727644dd81b6 100644 (file)
@@ -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.*;
index 99333874edf80516ff77aca36d33c08a2bfaea49..0e4873b5de16fbeed163a8e1e6b4f776546a5d74 100644 (file)
@@ -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;
-
 
 
 /**
index 7d451d7c76a5c4b57891d655dd7e11750af19419..a3668f6490246800d9ba33db2d7303775e4e3f31 100644 (file)
@@ -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];
index 3cd2c1c06264aa59e8cf651e7070a86a55155a7d..c134ffd5460176b2145ecf7d04cb62af1ae9eb5a 100644 (file)
@@ -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);
index 34345df5d0304aeb3ae106131b4f8f7966164365..c355afd440bee9a3842bd28cdcd6e8bd7759e850 100644 (file)
 
                <!-- Don't forget to update status.xml too! -->
         <release version="3.1-beta1" date="2008-??-??">
+           <action dev="POI-DEVELOPERS" type="add">44539 - Support for area references in formulas of rows >= 32768</action>
+           <action dev="POI-DEVELOPERS" type="add">44536 - Improved support for detecting read-only recommended files</action>
+           <action dev="POI-DEVELOPERS" type="fix">43901 - Correctly update the internal last cell number when adding and removing cells (previously sometimes off-by-one)</action>
+           <action dev="POI-DEVELOPERS" type="fix">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</action>
+           <action dev="POI-DEVELOPERS" type="fix">44504 - Added initial support for recognising external functions like YEARFRAC and ISEVEN (using NameXPtg), via LinkTable support</action>
+           <action dev="POI-DEVELOPERS" type="fix">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</action>
+           <action dev="POI-DEVELOPERS" type="fix">44504 - Fixed number conversion inconsistencies in many functions, and improved RefEval</action>
+           <action dev="POI-DEVELOPERS" type="fix">44508 - Fix formula evaluation with evaluateInCell on boolean formulas</action>
+           <action dev="POI-DEVELOPERS" type="fix">44510 - Fix how DVALRecord works with dropdowns</action>
+           <action dev="POI-DEVELOPERS" type="fix">44495 - Handle named cell ranges in formulas that have lower case parts</action>
+           <action dev="POI-DEVELOPERS" type="fix">44491 - Don't have the new-style "HPSF properties are always available" affect the old-style use of HPSF alongside HSSF</action>
+           <action dev="POI-DEVELOPERS" type="fix">44471 - Crystal Reports generates files with short StyleRecords, which isn't allowed in the spec. Work around this</action>
+           <action dev="POI-DEVELOPERS" type="add">44450 - Support for Lookup, HLookup and VLookup functions</action>
+           <action dev="POI-DEVELOPERS" type="fix">44449 - Avoid getting confused when two sheets have shared formulas for the same areas, and when the shared formula is set incorrectly</action>
+           <action dev="POI-DEVELOPERS" type="fix">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</action>
+           <action dev="POI-DEVELOPERS" type="add">44371 - Support for the Offset function</action>
+           <action dev="POI-DEVELOPERS" type="fix">38921 - Have HSSFPalette.findSimilar() work properly</action>
+           <action dev="POI-DEVELOPERS" type="fix">44456 - Fix the contrib SViewer / SViewerPanel to not fail on sheets with missing rows</action>
+           <action dev="POI-DEVELOPERS" type="fix">44403 - Further support for unusual, but valid, arguments to the Mid function</action>
+           <action dev="POI-DEVELOPERS" type="fix">44410 - Support for whole-column ranges, such as C:C, in formula strings and the formula evaluator</action>
+           <action dev="POI-DEVELOPERS" type="fix">44421 - Update Match function to properly support Area references</action>
+           <action dev="POI-DEVELOPERS" type="fix">44417 - Improved handling of references for the need to quote the sheet name for some formulas, but not when fetching a sheet by name</action>
+           <action dev="POI-DEVELOPERS" type="fix">44413 - Fix for circular references in INDEX, OFFSET, VLOOKUP formulas, where a cell is actually allowed to reference itself</action>
+           <action dev="POI-DEVELOPERS" type="fix">44403 - Fix for Mid function handling its arguments wrong</action>
+           <action dev="POI-DEVELOPERS" type="add">44364 - Support for Match, NA and SumProduct functions, as well as initial function error support</action>
            <action dev="POI-DEVELOPERS" type="fix">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.</action>
            <action dev="POI-DEVELOPERS" type="fix">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)</action>
            <action dev="POI-DEVELOPERS" type="fix">44373 - Have HSSFDateUtil.isADateFormat recognize more formats as being dates</action>
index 71ba941342633acea3f27465299014fc0cf48a51..eeab7c6e7973e8c247daf284eb6f54e3b3ab19fb 100644 (file)
      license.
    </p>
   </section>
+  <section><title>Publicly Available Information on the file formats</title>
+  <p>
+   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 <link href="http://www.microsoft.com/interop/osp">Open
+   Specification Promise</link>, which does allow us to use them for
+   building open source software under the <link 
+     href="http://www.apache.org/foundation/licence-FAQ.html">
+   Apache Software License</link>.
+  </p>
+  <p>
+   You can download the documentation on Excel, Word, PowerPoint and
+   Escher (drawing) from 
+   <link href="http://www.microsoft.com/interop/docs/OfficeBinaryFormats.mspx">http://www.microsoft.com/interop/docs/OfficeBinaryFormats.mspx</link>.
+   Documentation on a few of the supporting technologies used in these
+   file formats can be downloaded from
+   <link href="http://www.microsoft.com/interop/docs/supportingtechnologies.mspx">http://www.microsoft.com/interop/docs/supportingtechnologies.mspx</link>.
+  </p>
+  <p>
+   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.
+  </p>
+  <p>
+   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
+   <link href="http://www.ecma-international.org/publications/standards/Ecma-376.htm">http://www.ecma-international.org/publications/standards/Ecma-376.htm</link>,
+   and is also under the <link href="http://www.microsoft.com/interop/osp">OSP</link>.
+  </p>
+  <p>
+   It is also worth checking the documentation and code of the other
+   open source implementations of the file formats.
+  </p>
+  </section>
   <section><title>I just signed an NDA to get a spec from Microsoft and I'd like to contribute</title>
    <p>
      In short, stay away, stay far far away.  Implementing these file formats
    <p> 
      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)
    </p>
    <p>
      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.
    </p>
   </section>
  </section>
      <li>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.</li>
      <li>Get used to building POI, you'll be doing it a lot, be one with the build, know its targets, etc.</li>
      <li>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.</li>
-     <li>(HSSF)Get the Excel 97 Developer's Kit - its out of print but its dirt cheap (seen copies for under $15 (US)) used on <link href="http://www.amazon.com">amazon</link>.  It explains the Excel file format.</li>
+     <li>Download the file format documentation from Microsoft -
+      <link href="http://www.microsoft.com/interop/docs/OfficeBinaryFormats.mspx">OLE2 Binary File Formats</link> or
+      <link href="http://www.ecma-international.org/publications/standards/Ecma-376.htm">OOXML XML File Formats</link></li>
      <li>Submit patches (see below) of your contributions, modifications.</li>
      <li>Fill out new features, see <link href="http://issues.apache.org/bugzilla/buglist.cgi?product=POI">Bug database</link> for suggestions.</li>
    </ul>
index a6eaf67117c62e24cdc60e86ea4325699dabc452..438aab3f13cc505b44f3eb8f3e3143545468fb07 100755 (executable)
@@ -45,8 +45,8 @@
                 <link href="http://svn.apache.org/viewcvs.cgi/poi/trunk/src/scratchpad/">scratchpad area</link> 
                 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.
                        </note>
                        <p>The <link href="./quick-guide.html">quick guide</link> documentation provides 
             information on using this API. Comments and fixes gratefully accepted on the POI
index 91641b13ba2928bb376194c0eaa32736dcceaacd..c59cae1a3da4b4ed1d9e1f03d2183e9b40a96bc1 100644 (file)
        <!-- Don't forget to update changes.xml too! -->
     <changes>
         <release version="3.1-beta1" date="2008-??-??">
+           <action dev="POI-DEVELOPERS" type="add">44539 - Support for area references in formulas of rows >= 32768</action>
+           <action dev="POI-DEVELOPERS" type="add">44536 - Improved support for detecting read-only recommended files</action>
+           <action dev="POI-DEVELOPERS" type="fix">43901 - Correctly update the internal last cell number when adding and removing cells (previously sometimes off-by-one)</action>
+           <action dev="POI-DEVELOPERS" type="fix">44504 - Added initial support for recognising external functions like YEARFRAC and ISEVEN (using NameXPtg), via LinkTable support</action>
+           <action dev="POI-DEVELOPERS" type="fix">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</action>
+           <action dev="POI-DEVELOPERS" type="fix">44504 - Fixed number conversion inconsistencies in many functions, and improved RefEval</action>
+           <action dev="POI-DEVELOPERS" type="fix">44504 - Added initial support for recognising external functions like YEARFRAC and ISEVEN (using NameXPtg), via LinkTable support</action>
+           <action dev="POI-DEVELOPERS" type="fix">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</action>
+           <action dev="POI-DEVELOPERS" type="fix">44504 - Fixed number conversion inconsistencies in many functions, and improved RefEval</action>
+           <action dev="POI-DEVELOPERS" type="fix">44508 - Fix formula evaluation with evaluateInCell on boolean formulas</action>
+           <action dev="POI-DEVELOPERS" type="fix">44510 - Fix how DVALRecord works with dropdowns</action>
+           <action dev="POI-DEVELOPERS" type="fix">44495 - Handle named cell ranges in formulas that have lower case parts</action>
+           <action dev="POI-DEVELOPERS" type="fix">44491 - Don't have the new-style "HPSF properties are always available" affect the old-style use of HPSF alongside HSSF</action>
+           <action dev="POI-DEVELOPERS" type="fix">44471 - Crystal Reports generates files with short StyleRecords, which isn't allowed in the spec. Work around this</action>
+           <action dev="POI-DEVELOPERS" type="fix">44495 - Handle named cell ranges in formulas that have lower case parts</action>
+           <action dev="POI-DEVELOPERS" type="fix">44491 - Don't have the new-style "HPSF properties are always available" affect the old-style use of HPSF alongside HSSF</action>
+           <action dev="POI-DEVELOPERS" type="fix">44471 - Crystal Reports generates files with short StyleRecords, which isn't allowed in the spec. Work around this</action>
+           <action dev="POI-DEVELOPERS" type="add">44450 - Support for Lookup, HLookup and VLookup functions</action>
+           <action dev="POI-DEVELOPERS" type="fix">44449 - Avoid getting confused when two sheets have shared formulas for the same areas, and when the shared formula is set incorrectly</action>
+           <action dev="POI-DEVELOPERS" type="fix">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</action>
+           <action dev="POI-DEVELOPERS" type="add">44371 - Support for the Offset function</action>
+           <action dev="POI-DEVELOPERS" type="fix">38921 - Have HSSFPalette.findSimilar() work properly</action>
+           <action dev="POI-DEVELOPERS" type="fix">44456 - Fix the contrib SViewer / SViewerPanel to not fail on sheets with missing rows</action>
+           <action dev="POI-DEVELOPERS" type="fix">44403 - Further support for unusual, but valid, arguments to the Mid function</action>
+           <action dev="POI-DEVELOPERS" type="fix">44410 - Support for whole-column ranges, such as C:C, in formula strings and the formula evaluator</action>
+           <action dev="POI-DEVELOPERS" type="fix">44421 - Update Match function to properly support Area references</action>
+           <action dev="POI-DEVELOPERS" type="fix">44417 - Improved handling of references for the need to quote the sheet name for some formulas, but not when fetching a sheet by name</action>
+           <action dev="POI-DEVELOPERS" type="fix">44413 - Fix for circular references in INDEX, OFFSET, VLOOKUP formulas, where a cell is actually allowed to reference itself</action>
+           <action dev="POI-DEVELOPERS" type="fix">44403 - Fix for Mid function handling its arguments wrong</action>
+           <action dev="POI-DEVELOPERS" type="add">44364 - Support for Match, NA and SumProduct functions, as well as initial function error support</action>
            <action dev="POI-DEVELOPERS" type="fix">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.</action>
            <action dev="POI-DEVELOPERS" type="fix">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)</action>
            <action dev="POI-DEVELOPERS" type="fix">44373 - Have HSSFDateUtil.isADateFormat recognize more formats as being dates</action>
index 8d91c06e79a9e4e7dc14ebd52ec679b745301733..075fa4538118f8533704fe1ed220ef4a9626e946 100644 (file)
@@ -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);
index 832dbdef7a63b0b95dd4a6dc876a84e256b82b53..7b89b90d81f27193380e8f1f7d594bb228152be3 100644 (file)
@@ -1,4 +1,3 @@
-
 /* ====================================================================
    Licensed to the Apache Software Foundation (ASF) under one or more
    contributor license agreements.  See the NOTICE file distributed with
    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 :
  * <expression> ::= <term> [<addop> <term>]*
@@ -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.<br/>
+     * Primarily used by test cases when testing for specific parsing exceptions.</p>
+     *    
+     */
+    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 <b>not</b> 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));
-       
+
+    /**
+     * 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('(');
-        int numArgs = Arguments();
+        numArgs += Arguments(argumentPointers);
         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);
+        return getFunction(name, numArgs, argumentPointers);
     }
-    
+
     /**
      * Adds the size of all the ptgs after the provided index (inclusive).
      * <p>
@@ -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.
      * <p>
-     * 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(',');
-            }
-            else {
-              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;
             }
-            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());
+    private void powerFactor() {
+        percentFactor();
+        while(true) {
+            SkipWhite();
+            if(look != '^') {
+                return;
+            }
+            Match('^');
+            percentFactor();
+            tokens.add(new PowerPtg());
         }
-        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 percentFactor() {
+        tokens.add(parseSimpleFactor());
+        while(true) {
+            SkipWhite();
+            if(look != '%') {
+                return;
             }
-            
-            if (look == 'E') {
+            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();
-                
-                String sign = "";
-                if (look == '+') {
-                    GetChar();
-                } else if (look == '-') {
-                    GetChar();
-                    sign = "-";
-                }
-                
-                String number = GetNum();
-                if (number == null) {
-                    Expected("Integer");
-                }
-                exponent = sign + number;
+            } else if (look == '-') {
+                GetChar();
+                sign = "-";
             }
-            
-            if (number1 == null && number2 == null) {
-                Expected("Integer");
+
+            String number = GetNum();
+            if (number == null) {
+                throw expected("Integer");
             }
-            
-            tokens.add(getNumberPtgFromString(number1, number2, exponent));
+            exponent = sign + number;
+        }
+
+        if (number1 == null && number2 == null) {
+            throw expected("Integer");
         }
+
+        return getNumberPtgFromString(number1, number2, exponent);
     }
-    
-       /** 
-        * 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 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);
-           }
-           
+
+        if (number2 == null) {
+            number.append(number1);
+
+            if (exponent != null) {
+                number.append('E');
+                number.append(exponent);
+            }
+
             String numberStr = number.toString();
-            
+            int intVal;
             try {
-                return new IntPtg(numberStr);
+                intVal = Integer.parseInt(numberStr);
             } catch (NumberFormatException e) {
                 return new NumberPtg(numberStr);
             }
-           } else {
-            if (number1 != null) {
-                number.append(number1);
-            }
-            
-            number.append('.');
-            number.append(number2);
-            
-            if (exponent != null) {
-                number.append('E');
-                number.append(exponent);
+            if (IntPtg.isInRange(intVal)) {
+                return new IntPtg(intVal);
             }
-            
-            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());
+            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());
     }
-    
-    
-    /** Parse and Translate a Math Term */
-    private void  Term(){
-        Factor();
-                while (look == '*' || look == '/' || look == '^' || look == '&') {
+
+
+    private StringPtg parseStringLiteral()
+    {
+        Match('"');
         
-            ///TODO do we need to do anything here??
-            if (look == '*') Multiply();
-            else if (look == '/') Divide();
-            else if (look == '^') Power();
-            else if (look == '&') Concat();
+        StringBuffer token = new StringBuffer();
+        while (true) {
+            if (look == '"') {
+                GetChar();
+                if (look != '"') {
+                    break;
+                }
+             }
+            token.append(look);
+            GetChar();
         }
+        return new StringPtg(token.toString());
     }
-    
-    /** Recognize and Translate an Add */
-    private void Add() {
-        Match('+');
-        Term();
-        tokens.add(new AddPtg());
+
+    /** Parse and Translate a Math Term */
+    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 a Concatination */
-    private void Concat() {
-        Match('&');
-        Term();
-        tokens.add(new ConcatPtg());
+    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 test for Equality  */
-    private void Equal() {
-        Match('=');
-        Expression();
-        tokens.add(new EqualPtg());
+
+    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();
     }
     
-    /** Recognize and Translate a Subtract */
-    private void Subtract() {
-        Match('-');
-        Term();
-        tokens.add(new SubtractPtg());
-    }    
 
-    private void Power() {
-        Match('^');
-        Term();
-        tokens.add(new PowerPtg());
+    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<numOperands;i++) {
                 setParameterRVA(n.getChild(i),formulaType);
             }
-        } 
+        }
     }
     private void setParameterRVA(Node n, int expectedClass,int formulaType) {
-        Ptg p = (Ptg) n.getValue();
-        if (expectedClass == Ptg.CLASS_REF) { //pg 15, table 1 
+        Ptg p = n.getValue();
+        if (expectedClass == Ptg.CLASS_REF) { //pg 15, table 1
             if (p.getDefaultOperandClass() == Ptg.CLASS_REF ) {
                 setClass(n, Ptg.CLASS_REF);
             }
@@ -887,7 +883,7 @@ end;
             } else {
                 setClass(n,Ptg.CLASS_VALUE);
             }
-        } else { //Array class, pg 16. 
+        } else { //Array class, pg 16.
             if (p.getDefaultOperandClass() == Ptg.CLASS_VALUE &&
                  (formulaType==FORMULA_TYPE_CELL || formulaType == FORMULA_TYPE_SHARED)) {
                  setClass(n,Ptg.CLASS_VALUE);
@@ -896,9 +892,9 @@ end;
             }
         }
     }
-    
+
      private void setClass(Node n, byte theClass) {
-        Ptg p = (Ptg) n.getValue();
+        Ptg p = n.getValue();
         if (p instanceof AbstractFunctionPtg || !(p instanceof OperationPtg)) {
             p.setClass(theClass);
         } else {
@@ -909,7 +905,7 @@ end;
      }
     /**
      * Convience method which takes in a list then passes it to the
-     *  other toFormulaString signature. 
+     *  other toFormulaString signature.
      * @param book   workbook for 3D and named references
      * @param lptgs  list of Ptg, can be null or empty
      * @return a human readable String
@@ -930,9 +926,9 @@ end;
      * @return a human readable String
      */
     public String toFormulaString(List lptgs) {
-       return toFormulaString(book, lptgs);
+        return toFormulaString(book, lptgs);
     }
-    
+
     /**
      * Static method to convert an array of Ptgs in RPN order
      * to a human readable string format in infix mode.
@@ -941,61 +937,74 @@ end;
      * @return a human readable String
      */
     public static String toFormulaString(Workbook book, Ptg[] ptgs) {
-        if (ptgs == null || ptgs.length == 0) return "#NAME";
-        java.util.Stack stack = new java.util.Stack();
-        AttrPtg ifptg = null;
+        if (ptgs == null || ptgs.length == 0) {
+            // TODO - what is the justification for returning "#NAME" (which is not "#NAME?", btw)
+            return "#NAME";
+        }
+        Stack stack = new Stack();
 
            // Excel allows to have AttrPtg at position 0 (such as Blanks) which
            // do not have any operands. Skip them.
-        stack.push(ptgs[0].toFormulaString(book));
-                  
-        for (int i = 1; i < ptgs.length; i++) {
-            if (! (ptgs[i] instanceof OperationPtg)) {
-                stack.push(ptgs[i].toFormulaString(book));
+        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;
+        }
+
+        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 (ptgs[i] instanceof AttrPtg && ((AttrPtg) ptgs[i]).isOptimizedIf()) {
-                ifptg = (AttrPtg) ptgs[i];
+            if (! (ptg instanceof OperationPtg)) {
+                stack.push(ptg.toFormulaString(book));
                 continue;
             }
-                      
-            final OperationPtg o = (OperationPtg) ptgs[i];
-            final String[] operands = new String[o.getNumberOfOperands()];
 
-            for (int j = operands.length; j > 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;
+            if (ptg instanceof AttrPtg && ((AttrPtg) ptg).isOptimizedIf()) {
+                continue;
+            }
 
-            final AbstractFunctionPtg f = (AbstractFunctionPtg) o;
-            final String fname = f.getName();
-            if (fname == null) continue;
+            final OperationPtg o = (OperationPtg) ptg;
+            int nOperands = o.getNumberOfOperands();
+            final String[] operands = new String[nOperands];
 
-            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));
+            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));
         }
-    }
-        // TODO: catch stack underflow and throw parse exception.
-        return (String) stack.pop();
+        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<numPtgs;i++) {
             if (tokens.get(i) instanceof OperationPtg) {
-                
+
                 o = (OperationPtg) tokens.get(i);
                 numOperands = o.getNumberOfOperands();
                 operands = new Node[numOperands];
                 for (int j=0;j<numOperands;j++) {
-                    operands[numOperands-j-1] = (Node) stack.pop(); 
+                    operands[numOperands-j-1] = (Node) stack.pop();
                 }
                 Node result = new Node(o);
                 result.setChildren(operands);
@@ -1036,7 +1045,7 @@ end;
         }
         return (Node) stack.pop();
     }
-   
+
     /** toString on the parser instance returns the RPN ordered list of tokens
      *   Useful for testing
      */
@@ -1045,21 +1054,21 @@ end;
            for (int i=0;i<tokens.size();i++) {
             buf.append( ( (Ptg)tokens.get(i)).toFormulaString(book));
             buf.append(' ');
-        } 
+        }
         return buf.toString();
     }
-    
-}   
+
     /** Private helper class, used to create a tree representation of the formula*/
-    class Node {
+    private static final class Node {
         private Ptg value=null;
         private Node[] children=new Node[0];
         private int numChild=0;
         public Node(Ptg val) {
-            value = val; 
+            value = val;
         }
         public void setChildren(Node[] child) {children = child;numChild=child.length;}
         public int getNumChildren() {return numChild;}
         public Node getChild(int number) {return children[number];}
         public Ptg getValue() {return value;}
     }
+}
diff --git a/src/java/org/apache/poi/hssf/model/LinkTable.java b/src/java/org/apache/poi/hssf/model/LinkTable.java
new file mode 100755 (executable)
index 0000000..88c94a6
--- /dev/null
@@ -0,0 +1,302 @@
+/* ====================================================================
+   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.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.poi.hssf.record.CRNCountRecord;
+import org.apache.poi.hssf.record.CRNRecord;
+import org.apache.poi.hssf.record.CountryRecord;
+import org.apache.poi.hssf.record.ExternSheetRecord;
+import org.apache.poi.hssf.record.ExternSheetSubRecord;
+import org.apache.poi.hssf.record.ExternalNameRecord;
+import org.apache.poi.hssf.record.NameRecord;
+import org.apache.poi.hssf.record.Record;
+import org.apache.poi.hssf.record.SupBookRecord;
+
+/**
+ * Link Table (OOO pdf reference: 4.10.3 ) <p/>
+ *
+ * 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.
+ *  <p/>
+ *
+ *  In BIFF8 the Link Table consists of
+ *  <ul>
+ *  <li>one or more EXTERNALBOOK Blocks<p/>
+ *     each consisting of
+ *     <ul>
+ *     <li>exactly one EXTERNALBOOK (0x01AE) record</li>
+ *     <li>zero or more EXTERNALNAME (0x0023) records</li>
+ *     <li>zero or more CRN Blocks<p/>
+ *                     each consisting of
+ *             <ul>
+ *             <li>exactly one XCT (0x0059)record</li>
+ *             <li>zero or more CRN (0x005A) records (documentation says one or more)</li>
+ *             </ul>
+ *     </li>
+ *     </ul>
+ *  </li>
+ *  <li>exactly one EXTERNSHEET (0x0017) record</li>
+ *  <li>zero or more DEFINEDNAME (0x0018) records</li>
+ *  </ul>
+ *
+ *
+ * @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 (executable)
index 0000000..03177c7
--- /dev/null
@@ -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 <tt>Record</tt> 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. <code>null</code> if this stream is exhausted.
+        */
+       public Class peekNextClass() {
+               if(_nextIndex >= _list.size()) {
+                       return null;
+               }
+               return _list.get(_nextIndex).getClass();
+       }
+
+       public int getCountRead() {
+               return _countRead;
+       }
+}
index 2ba50857ca6fbe39371e1c6aa3e40604e9b5b35b..8fa3010a4b2ec6cbe64276d32d5e0db19ff1909b 100644 (file)
@@ -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;
-    }
-
-    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;
+        return getOrCreateLinkTable().checkExternSheet(sheetNumber);
     }
 
-
-
     /** 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 (executable)
index 0000000..4c9e442
--- /dev/null
@@ -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 \96 CRN Count <P>
+ *
+ * REFERENCE:  5.114<P>
+ *
+ * @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 (executable)
index 0000000..73b9e42
--- /dev/null
@@ -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  <P>
+ * Description: This record stores the contents of an external cell or cell range <P>
+ * REFERENCE:  5.23<P>
+ *
+ * @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;
+       }
+}
index 2846f5066c3b93df9ab2d904d6cbb19f04ad9abc..011c0a435557efab9fc1ae33deb473f4ab3556a8 100644 (file)
@@ -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<P>
+ * Title:        DATAVALIDATIONS Record<P>
  * 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 (executable)
index 0000000..771603c
--- /dev/null
@@ -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<p/>
+ * 
+ * @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 <code>true</code>, 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();
+    }
+}
index 17f8cda64a14fbcf06959b98cdc4db4d7ffe401c..2f56eb092a0b481ac460ca2a8cbaed5311d2d051 100644 (file)
@@ -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
index 3b1c413d58052c702ea05e33d697d7c94c3868cb..a06bc8aedd7c06761d5ae4c2dadbd605a6c091bf 100644 (file)
@@ -726,7 +726,7 @@ public class NameRecord extends Record {
                for(int i=0; i<refs.length; i++) {
                    ptg = new Area3DPtg();
                    ((Area3DPtg) ptg).setExternSheetIndex(externSheetIndex);
-                   ((Area3DPtg) ptg).setArea(refs[i].toString());
+                   ((Area3DPtg) ptg).setArea(refs[i].formatAsString());
                    field_13_name_definition.push(ptg);
                    this.setDefinitionTextLength( (short)(getDefinitionLength() + ptg.getSize()) );
                }
index 0f164b44735ae4ac4c58df02af4dd2206bf37a97..f247a8d4b9b3d24435455183546ff9612cd50837 100644 (file)
@@ -77,7 +77,11 @@ public class RecordFactory
                 NoteRecord.class, ObjectProtectRecord.class, ScenarioProtectRecord.class, 
                 FileSharingRecord.class, ChartTitleFormatRecord.class,
                 DVRecord.class, DVALRecord.class, UncalcedRecord.class,
-                HyperlinkRecord.class
+                HyperlinkRecord.class,
+                ExternalNameRecord.class, // TODO - same changes in non-@deprecated version of this class
+                SupBookRecord.class,
+                CRNCountRecord.class,
+                CRNRecord.class,
             };
     }
     private static Map           recordsMap  = recordsToMap(records);
index 32094287fa73f26725c2d7cac219cb6f30812163..431558ccc8a2fd9cdf1cf831c4f090f3d9300256 100755 (executable)
@@ -267,7 +267,7 @@ public class RecordInputStream extends InputStream
     
   public String readCompressedUnicode(int length) {
     if ((length < 0) || ((remaining() < length) && !isContinueNext())) {
-            throw new IllegalArgumentException("Illegal length");
+            throw new IllegalArgumentException("Illegal length " + length);
     }
 
     StringBuffer buf = new StringBuffer(length);
index ae250246d3a4e7b5141a366c3b1ffffa4886c43e..b2fd9ae4f55ea1a3349daeb03b143c0b312f9135 100755 (executable)
@@ -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,8 +14,7 @@
    See the License for the specific language governing permissions and
    limitations under the License.
 ==================================================================== */
-        
-
 package org.apache.poi.hssf.record;
 
 import java.util.Stack;
@@ -34,10 +32,7 @@ import org.apache.poi.hssf.record.formula.*;
  * record types.
  * @author Danny Mui at apache dot org
  */
-
-public class SharedFormulaRecord
-    extends Record
-{
+public final class SharedFormulaRecord extends Record {
         public final static short   sid = 0x4BC;
     
     private int               field_1_first_row;
@@ -58,15 +53,15 @@ public class SharedFormulaRecord
 
     public SharedFormulaRecord(RecordInputStream in)
     {
-         super(in);
+          super(in);
     }
     
     protected void validateSid(short id)
     {
-               if (id != this.sid)
-               {
-                       throw new RecordFormatException("Not a valid SharedFormula");
-               }        
+        if (id != this.sid)
+        {
+            throw new RecordFormatException("Not a valid SharedFormula");
+        }        
     }    
     
     public int getFirstRow() {
@@ -96,14 +91,14 @@ public class SharedFormulaRecord
 
     public int serialize(int offset, byte [] data)
     {
-       //Because this record is converted to individual Formula records, this method is not required.
-       throw new UnsupportedOperationException("Cannot serialize a SharedFormulaRecord");
+        //Because this record is converted to individual Formula records, this method is not required.
+        throw new UnsupportedOperationException("Cannot serialize a SharedFormulaRecord");
     }
 
     public int getRecordSize()
     {
-       //Because this record is converted to individual Formula records, this method is not required.
-       throw new UnsupportedOperationException("Cannot get the size for a SharedFormulaRecord");
+        //Because this record is converted to individual Formula records, this method is not required.
+        throw new UnsupportedOperationException("Cannot get the size for a SharedFormulaRecord");
 
     }
 
@@ -257,39 +252,40 @@ public class SharedFormulaRecord
       }
     }
     
-    private short fixupRelativeColumn(int currentcolumn, short column, boolean relative) {
-       if(relative) {
-               if((column&128)!=0) column=(short)(column-256);
-               column+=currentcolumn;
-       }
-       return column;
-       }
-
-       private short fixupRelativeRow(int currentrow, short row, boolean relative) {
-               if(relative) {
-                       row+=currentrow;
-               }
-               return row;
-       }
-
-       /**
-        * Mirroring formula records so it is registered in the ValueRecordsAggregate
-        */
-       public boolean isInValueSection()
-       {
-                return true;
-       }
-
-
-        /**
-         * Register it in the ValueRecordsAggregate so it can go into the FormulaRecordAggregate
-         */
-        public boolean isValue() {
-               return true;
-        }
+    private int fixupRelativeColumn(int currentcolumn, int column, boolean relative) {
+        if(relative) {
+            // mask out upper bits to produce 'wrapping' at column 256 ("IV")
+            return (column + currentcolumn) & 0x00FF;
+        }
+        return column;
+    }
+
+    private int fixupRelativeRow(int currentrow, int row, boolean relative) {
+        if(relative) {
+            // mask out upper bits to produce 'wrapping' at row 65536
+            return (row+currentrow) & 0x00FFFF;
+        }
+        return row;
+    }
+
+    /**
+     * Mirroring formula records so it is registered in the ValueRecordsAggregate
+     */
+    public boolean isInValueSection()
+    {
+         return true;
+    }
+
+
+     /**
+      * Register it in the ValueRecordsAggregate so it can go into the FormulaRecordAggregate
+      */
+     public boolean isValue() {
+         return true;
+     }
 
     public Object clone() {
-       //Because this record is converted to individual Formula records, this method is not required.
-       throw new UnsupportedOperationException("Cannot clone a SharedFormulaRecord");
+        //Because this record is converted to individual Formula records, this method is not required.
+        throw new UnsupportedOperationException("Cannot clone a SharedFormulaRecord");
     }
 }
index 9f45e3adf9c786afd5c0cc02f949ffa82adcf55b..65ca23cf34324e29773cd8e38277439971348320 100644 (file)
@@ -88,13 +88,18 @@ public class StyleRecord
         else if (getType() == STYLE_USER_DEFINED)
         {
             field_2_name_length = in.readShort();
-            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);
+            // Some files from Crystal Reports lack
+            //  the remaining fields, which is naughty
+            if(in.remaining() > 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);
+                   }
             }
         }
 
index 91a16f0793451561008b01d3efde216d58ea1ba4..6755aa6f8b5fc61e54cdad056536ad468bea056f 100644 (file)
@@ -1,4 +1,3 @@
-
 /* ====================================================================
    Licensed to the Apache Software Foundation (ASF) under one or more
    contributor license agreements.  See the NOTICE file distributed with
    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  <P>
- * Description:  A Extrenal Workbook Deciption (Sup Book)
+ * Title:        Sup Book (EXTERNALBOOK) <P>
+ * Description:  A External Workbook Description (Suplemental Book)
  *               Its only a dummy record for making new ExternSheet Record <P>
- * REFERENCE:  <P>
+ * REFERENCE:  5.38<P>
  * @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 short             field_1_number_of_sheets;
-    private short             field_2_flag;
 
+    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;
 
-    public SupBookRecord()
-    {
-        setFlag((short)0x401);
+    private short             field_1_number_of_sheets;
+    private UnicodeString     field_2_encoded_url;
+    private UnicodeString[]   field_3_sheet_names;
+    private boolean           _isAddInFunctions;
+
+    
+    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");
         }
     }
 
+    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;
+    }
     /**
-     * @param in the RecordInputstream to read the record from
+     * 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)
-    {
-        //For now We use it only for one case
-        //When we need to add an named range when no named ranges was
-        //before it
+    protected void fillFields(RecordInputStream in) {
         field_1_number_of_sheets = in.readShort();
-        field_2_flag = 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<field_3_sheet_names.length; i++) {
+            urs = new UnicodeRecordStats();
+            field_3_sheet_names[i].getRecordSize(urs);
+            sum += urs.recordSize;
+        }
+        return sum;
     }
 
     /**
@@ -92,14 +178,30 @@ public class SupBookRecord extends Record
      * @param data byte array containing instance data
      * @return number of bytes written
      */
-    public int serialize(int offset, byte [] data)
-    {
+    public int serialize(int offset, byte [] data) {
         LittleEndian.putShort(data, 0 + offset, sid);
-        LittleEndian.putShort(data, 2 + offset, (short) 4);
+        int dataSize = getDataSize();
+        LittleEndian.putShort(data, 2 + offset, (short) dataSize);
         LittleEndian.putShort(data, 4 + offset, field_1_number_of_sheets);
-        LittleEndian.putShort(data, 6 + offset, field_2_flag);
-
-        return getRecordSize();
+               
+        if(isExternalReferences()) {
+            
+            int currentOffset = 6 + offset;
+            UnicodeRecordStats urs = new UnicodeRecordStats();
+            field_2_encoded_url.serialize(urs, currentOffset, data);
+            currentOffset += urs.recordSize;
+            
+            for(int i=0; i<field_3_sheet_names.length; i++) {
+                urs = new UnicodeRecordStats();
+                field_3_sheet_names[i].serialize(urs, currentOffset, data);
+                currentOffset += urs.recordSize;
+            }
+        } else {
+            short field2val = _isAddInFunctions ? TAG_ADD_IN_FUNCTIONS : TAG_INTERNAL_REFERENCES;
+            
+            LittleEndian.putShort(data, 6 + offset, field2val);
+        }
+        return dataSize + 4;
     }
 
     public void setNumberOfSheets(short number){
@@ -110,21 +212,18 @@ public class SupBookRecord extends Record
         return field_1_number_of_sheets;
     }
 
-    public void setFlag(short flag){
-        field_2_flag = flag;
-    }
-
-    public short getFlag() {
-        return field_2_flag;
-    }
-
-    public int getRecordSize()
-    {
-        return 4 + 4;
+    public int getRecordSize() {
+        return getDataSize() + 4;
     }
 
     public short getSid()
     {
         return sid;
     }
+    public UnicodeString getURL() {
+        return field_2_encoded_url;
+    }
+    public UnicodeString[] getSheetNames() {
+        return (UnicodeString[]) field_3_sheet_names.clone();
+    }
 }
index e48a0a902bf84f8bfbabb7b9645dab81c2172fda..068896952906387710d6b9892750f20da27085fe 100644 (file)
@@ -34,7 +34,7 @@ import java.util.List;
  * @author Jason Height (jheight at chariot dot net dot au)
  */
 
-public class ValueRecordsAggregate
+public final class ValueRecordsAggregate
     extends Record
 {
     public final static short sid       = -1000;
@@ -127,7 +127,7 @@ public class ValueRecordsAggregate
 
         FormulaRecordAggregate lastFormulaAggregate = null;
         
-        // First up, locate all the shared formulas
+        // First up, locate all the shared formulas for this sheet
         List sharedFormulas = new java.util.ArrayList();
         for (k = offset; k < records.size(); k++)
         {
@@ -135,6 +135,10 @@ public class ValueRecordsAggregate
             if (rec instanceof SharedFormulaRecord) {
                sharedFormulas.add(rec);
             }
+            if(rec instanceof EOFRecord) {
+                // End of current sheet. Ignore all subsequent shared formula records (Bugzilla 44449)
+                break;
+            }
         }
 
         // Now do the main processing sweep
@@ -156,6 +160,8 @@ public class ValueRecordsAggregate
                 //  for us
                 boolean found = false;
                 for (int i=sharedFormulas.size()-1;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 <tt>SharedFormulaRecord.convertSharedFormulaRecord</tt> and hence the 
+     * <tt>parsedExpression</tt> field of this <tt>FormulaRecord</tt> will not get updated.<br/>
+     * As it turns out, this is not a problem, because in these circumstances, the existing value
+     * for <tt>parsedExpression</tt> is perfectly OK.<p/>
+     * 
+     * This method may also be used for setting breakpoints to help diagnose issues regarding the
+     * abnormally-set 'shared formula' flags. 
+     * (see TestValueRecordsAggregate.testSpuriousSharedFormulaFlag()).<p/>
+     * 
+     * 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 (executable)
index 0000000..6ec831e
--- /dev/null
@@ -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.<p/>
+ * 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;
+       }
+}
index 555c50294937425b21715aed8ff11c17e57f9248..c7c57280d32e5851d4509e8b1cd7a9e00374f4e1 100644 (file)
@@ -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 <code>true</code> 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;i<operands.length;i++) {
-                  buf.append(operands[i]);
-                  buf.append(',');
-              }
-              buf.deleteCharAt(buf.length()-1);
-          }
-          if (field_2_fnc_index != 1) {
-            buf.append(")");
-          }
+        
+        if(isExternalFunction()) {
+            buf.append(operands[0]); // first operand is actually the function name
+            appendArgs(buf, 1, operands);
+        } else {
+            buf.append(getName());
+            appendArgs(buf, 0, operands);
+        }
         return buf.toString();
     }
+
+    private static void appendArgs(StringBuffer buf, int firstArgIx, String[] operands) {
+        buf.append('(');
+        for (int i=firstArgIx;i<operands.length;i++) {
+            if (i>firstArgIx) {
+                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
+     * <p>
+     * The name matching is case insensitive.
+     * @return <code>true</code> if the name specifies a standard worksheet function, 
+     *  <code>false</code> 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.
+     * <p>
+     * The name matching is case insensitive.
+     * @return the standard worksheet function index if found, otherwise <tt>FUNCTION_INDEX_EXTERNAL</tt>
+     */
+    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");
index ac260ffa4eb318d7ebb77e6b97b252eddc5ed934..33278e25edceb844f78adbfbe9ac351cf8d7a904 100644 (file)
 
 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];
-               }
-
-               setFirstRow(    (short) firstCell.getRow() );
-               setFirstColumn( (short) firstCell.getCol() );
-               setLastRow(     (short) lastCell.getRow() );
-               setLastColumn(  (short) lastCell.getCol() );
-               setFirstColRelative( !firstCell.isColAbsolute() );
+               CellReference frstCell = ar.getFirstCell();
+               CellReference lastCell = ar.getLastCell();
+
+               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;
        }
 
index 57628f19b8825178fdfab9d8324711dd98a8d85d..515d07dd41535e64601be7c90be6dfcadb8390eb 100644 (file)
@@ -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 (file)
index 0000000..5a0a21e
--- /dev/null
@@ -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
index 908c8d5e3cce9bbcaf1619f98519cd79dee3b0d9..be34e0074a3139547467be6ac203d334535d4306 100644 (file)
@@ -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
      * <code>false</code>
      */
     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() {
index 2974eec64ed7c128c5fdc604140eca08905cd85f..42dc11fa326772bb356b139681f6196983b9f930 100644 (file)
@@ -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);
     }
 
index 372b1850e4bf7127ccf56332181ea51dd15bda69..12166b7967ecc639f2baef58b896b019a42192d7 100644 (file)
@@ -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<token_1_columns;x++) {
-               for (int y=0;y<token_2_rows;y++) {
-                       byte grbit = in.readByte();
-                       if (grbit == 0x01) {
-                           token_3_arrayValues[x][y] = new Double(in.readDouble());
-                       } else if (grbit == 0x02) {
-                               //Ignore the doco, it is actually a unicode string with all the
-                               //trimmings ie 16 bit size, option byte etc
-                               token_3_arrayValues[x][y] = in.readUnicodeString();
-                       } else throw new RecordFormatException("Unknown grbit '"+grbit+"'");
-               }
+                       for (int y=0;y<token_2_rows;y++) {
+                               byte grbit = in.readByte();
+                               if (grbit == 0x01) {
+                                       token_3_arrayValues[x][y] = new Double(in.readDouble());
+                               } else if (grbit == 0x02) {
+                                       //Ignore the doco, it is actually a unicode string with all the
+                                       //trimmings ie 16 bit size, option byte etc
+                                       token_3_arrayValues[x][y] = in.readUnicodeString();
+                               } else throw new RecordFormatException("Unknown grbit '"+grbit+"' at " + x + "," + y + " with " + in.remaining() + " bytes left");
+                       }
         }
-
     }
 
     public String toString()
index 34bad6f32c359e2a3085c2c9625c73be3c4af8e3..26cc2e027bc9f9147a747e5ba807124722ffb459 100644 (file)
@@ -26,66 +26,67 @@ import org.apache.poi.hssf.usermodel.HSSFErrorConstants;
 /**
  * @author Daniel Noll (daniel at nuix dot com dot au)
  */
-public class ErrPtg extends Ptg
-{
+public final class ErrPtg extends Ptg {
+    
+    // convenient access to namespace
+    private static final HSSFErrorConstants EC = null;
+    
+    /** <b>#NULL!</b>  - Intersection of two cell ranges is empty */
+    public static final ErrPtg NULL_INTERSECTION = new ErrPtg(EC.ERROR_NULL); 
+    /** <b>#DIV/0!</b> - Division by zero */
+    public static final ErrPtg DIV_ZERO = new ErrPtg(EC.ERROR_DIV_0);
+    /** <b>#VALUE!</b> - Wrong type of operand */
+    public static final ErrPtg VALUE_INVALID = new ErrPtg(EC.ERROR_VALUE);
+    /** <b>#REF!</b> - Illegal or deleted cell reference */
+    public static final ErrPtg REF_INVALID = new ErrPtg(EC.ERROR_REF);
+    /** <b>#NAME?</b> - Wrong function or range name */
+    public static final ErrPtg NAME_INVALID = new ErrPtg(EC.ERROR_NAME); 
+    /** <b>#NUM!</b> - Value range overflow */
+    public static final ErrPtg NUM_ERROR = new ErrPtg(EC.ERROR_NUM);
+    /** <b>#N/A</b> - 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;
     }
 }
index 410364971c6c12106a07d5bee6ec8444b8e5a977..1123fc803af6eae2f14394b5a2bfc44bce2c7731 100644 (file)
@@ -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) {
index 257c089df8b297f48e1687772a9825578409ebf8..f4106b6aa8fc221e7ba949b4e5213157ab392d5d 100644 (file)
@@ -1,4 +1,3 @@
-
 /* ====================================================================
    Licensed to the Apache Software Foundation (ASF) under one or more
    contributor license agreements.  See the NOTICE file distributed with
    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 <code>true</code> if the specified value is within the range of values 
+     * <tt>IntPtg</tt> 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();
+    }
 }
index d418afa7e756b5c3e3a1f30b37e1ebbe4f6ae3f6..5405481a09a315468c113d12f7769c0d112e3eb6 100644 (file)
@@ -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)
     {
index b1f280a017fec032ca78a421a91b999597102aa3..ccf5ab6fcd342551ac34ae9352115b5e1d133c98 100644 (file)
@@ -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;}
index 510eebb037e9ac2f4dab15a996d848e6284ad98e..84ff659b333de826d406de4210794d0df5dcf914 100644 (file)
 
 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.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.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.util.LittleEndian;
 
 /**
  * Title:        Reference 3D Ptg <P>
@@ -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;
    }
-
 }
index 996f40e393229775db15c41e13525a8241e0d914..596b386235f0d165e38affda764b41b0eb8f5057 100644 (file)
@@ -1,4 +1,3 @@
-
 /* ====================================================================
    Licensed to the Apache Software Foundation (ASF) under one or more
    contributor license agreements.  See the NOTICE file distributed with
    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);
     }
 
index b9d55a09ec18ee5125c7306431cd80336ae86473..8a6b2c03b4fa8352208c0c59117d8f0728bfce02 100644 (file)
@@ -1,4 +1,3 @@
-
 /* ====================================================================
    Licensed to the Apache Software Foundation (ASF) under one or more
    contributor license agreements.  See the NOTICE file distributed with
 
 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);
   }
 
index df3e5a70bcbe056ad3b39d73eea02cc75bc6a450..4983c9d0706aabe0a4a71d5a087c9105c73e44af 100644 (file)
@@ -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() {
index ba796db3b2475200425cac7ab6caafb3eff8b49c..8e47cbe7a0fc8fd034e56fbdd5c2ead788aa28d2 100755 (executable)
@@ -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();
index 67f4557972bf4209e7a8371a97b09919b4e3e5e7..f906e91a49c9ec78fb25731a6cba2c9cf35e5c69 100644 (file)
@@ -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(formulabook);
             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.<p/>
+     * 
+     * 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.
index 1f5ec13c3a437916f6c2bd5244f4608f5f427f5e..89c25d1e8727b2030c2d0c1eb8c7d12b9a76d313 100644 (file)
    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
+    }
+    
+    /** <b>#NULL!</b>  - Intersection of two cell ranges is empty */
+    public static final int ERROR_NULL = 0x00;
+    /** <b>#DIV/0!</b> - Division by zero */
+    public static final int ERROR_DIV_0 = 0x07;
+    /** <b>#VALUE!</b> - Wrong type of operand */
+    public static final int ERROR_VALUE = 0x0F; 
+    /** <b>#REF!</b> - Illegal or deleted cell reference */
+    public static final int ERROR_REF = 0x17;  
+    /** <b>#NAME?</b> - Wrong function or range name */
+    public static final int ERROR_NAME = 0x1D; 
+    /** <b>#NUM!</b> - Value range overflow */
+    public static final int ERROR_NUM = 0x24; 
+    /** <b>#N/A</b> - 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 <code>true</code> 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;
+    }
 }
index 42773d4a33255876faf9148fed32bc6563abf04e..0a31728899b49634426384aed0c39cec26cc3155 100644 (file)
@@ -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);
             }
         }
index 0c9807b5aadaef69167fc044f3653b2c23b4aaa0..54229a16abb0ca7955b9e14af87b1bfd5a59857c 100644 (file)
@@ -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<cells.length)
+        short column=cell.getCellNum();
+        if(cell!=null && column<cells.length)
         {
           cells[column]=null;
         }
 
-        if (hcell.getCellNum() == row.getLastCol())
+        if (cell.getCellNum() == row.getLastCol())
         {
             row.setLastCol(findLastCell(row.getLastCol()));
         }
-        if (hcell.getCellNum() == row.getFirstCol())
+        if (cell.getCellNum() == row.getFirstCol())
         {
             row.setFirstCol(findFirstCell(row.getFirstCol()));
         }
@@ -471,6 +468,7 @@ public class HSSFRow
      * @return cell iterator of the physically defined cells.  Note element 4 may
      * actually be row cell depending on how many are defined!
      */
+
     public Iterator cellIterator()
     {
       return new CellIterator();
index 0a64cee358ed9686d2eff38b954a6510d9b78109..047d7fcb3b4652ba0aa88252b890ee65ed0f8abf 100644 (file)
@@ -414,7 +414,7 @@ public class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet
 
        //formula fields ( size and data )
        String str_formula = obj_validation.getFirstFormula();
-       FormulaParser fp = new FormulaParser(str_formula+";",book);
+       FormulaParser fp = new FormulaParser(str_formulabook);
        fp.parse();
        Stack ptg_arr = new Stack();
        Ptg[] ptg  = fp.getRPNPtg();
@@ -438,7 +438,7 @@ public class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet
        if ( obj_validation.getSecondFormula() != null )
        {
          str_formula = obj_validation.getSecondFormula();
-         fp = new FormulaParser(str_formula+";",book);
+         fp = new FormulaParser(str_formulabook);
          fp.parse();
          ptg_arr = new Stack();
          ptg  = fp.getRPNPtg();
@@ -642,10 +642,17 @@ public class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet
     }
 
     /**
-     * Determine whether printed output for this sheet will be vertically centered.
+     * TODO: Boolean not needed, remove after next release
+     * @deprecated use getVerticallyCenter() instead
      */
+    public boolean getVerticallyCenter(boolean value) {
+        return getVerticallyCenter();
+    }
 
-    public boolean getVerticallyCenter(boolean value)
+    /**
+     * Determine whether printed output for this sheet will be vertically centered.
+     */
+    public boolean getVerticallyCenter()
     {
         VCenterRecord record =
                 (VCenterRecord) sheet.findFirstRecordBySid(VCenterRecord.sid);
index 972051e87a177bbf6d0c66745368900159889d27..686bd433d7311b38196724a098999461467bff39 100644 (file)
@@ -180,10 +180,7 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm
             throws IOException
     {
         this.preserveNodes = preserveNodes;
-
-        // Read in the HPSF properties
         this.filesystem = fs;
-        readProperties();
         
         // If we're not preserving nodes, don't track the
         //  POIFS any more
@@ -1134,12 +1131,12 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm
        public void setPrintArea(int sheetIndex, int startColumn, int endColumn,
                                                          int startRow, int endRow) {
 
-               //using absolute references because they dont get copied and pasted anyway
+               //using absolute references because they don't get copied and pasted anyway
                CellReference cell = new CellReference(startRow, startColumn, true, true);
-               String reference = cell.toString();
+               String reference = cell.formatAsString();
 
                cell = new CellReference(endRow, endColumn, true, true);
-               reference = reference+":"+cell.toString();
+               reference = reference+":"+cell.formatAsString();
 
                setPrintArea(sheetIndex, reference);
        }
@@ -1397,6 +1394,13 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm
         }
     }
 
+    /**
+     * Is the workbook protected with a password (not encrypted)?
+     */
+    public boolean isWriteProtected() {
+       return this.workbook.isWriteProtected();
+    }
+    
     /**
      * protect a workbook with a password (not encypted, just sets writeprotect
      * flags and the password.
index 8169378e7657900daae42b725b8362e87a944cf8..3f6b321a6e5fe8711cc13d3dfda7a314847584b5 100644 (file)
@@ -21,28 +21,63 @@ package org.apache.poi.hssf.util;
 import java.util.ArrayList;
 import java.util.StringTokenizer;
 
-public class AreaReference {
+public final class AreaReference {
 
-
-private CellReference [] cells;
-private int dim;
+    /** The character (!) that separates sheet names from cell references */ 
+    private static final char SHEET_NAME_DELIMITER = '!';
+    /** The character (:) that separates the two cell references in a multi-cell area reference */
+    private static final char CELL_DELIMITER = ':';
+    /** The character (') used to quote sheet names when they contain special characters */
+    private static final char SPECIAL_NAME_DELIMITER = '\'';
+    
+    private final CellReference _firstCell;
+    private final CellReference _lastCell;
+    private final boolean _isSingleCell;
 
     /**
-     * Create an area ref from a string representation.
-     * The area reference must be contiguous
+     * Create an area ref from a string representation.  Sheet names containing special characters should be
+     * delimited and escaped as per normal syntax rules for formulas.<br/> 
+     * 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<dim;i++) {
-            cells[i]=new CellReference(refs[i]);
+        String[] parts = separateAreaRefs(reference);
+        
+        // Special handling for whole-column references
+        if(parts.length == 2 && parts[0].length() == 1 &&
+                       parts[1].length() == 1 && 
+                       parts[0].charAt(0) >= '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;
+    }
 
     /**
      * Is the reference for a contiguous (i.e.
@@ -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 <code>false</code> if this area reference involves more than one cell
+     */
+    public boolean isSingleCell() {
+        return _isSingleCell;
     }
-    /** 
-     * Return the cell references that define this area
-     * (i.e. the two corners) 
+    
+    /**
+     * @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[] getCells() {
-        return cells;
+   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 <tt>getFirstCell()</tt>
+     * @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<dim;i++){
-            retval.append(':');
-            retval.append(cells[i].toString());
+    /**
+     *  Example return values:
+     *    <table border="0" cellpadding="1" cellspacing="0" summary="Example return values">
+     *      <tr><th align='left'>Result</th><th align='left'>Comment</th></tr>
+     *      <tr><td>A1:A1</td><td>Single cell area reference without sheet</td></tr>
+     *      <tr><td>A1:$C$1</td><td>Multi-cell area reference without sheet</td></tr>
+     *      <tr><td>Sheet1!A$1:B4</td><td>Standard sheet name</td></tr>
+     *      <tr><td>'O''Brien''s Sales'!B5:C6'&nbsp;</td><td>Sheet name with special characters</td></tr>
+     *    </table>
+     * @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 <code>null</code>
      */
-    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<len; i++) {
+            switch(reference.charAt(i)) {
+                case CELL_DELIMITER:
+                    if(!insideDelimitedName) {
+                        if(delimiterPos >=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;
+            }
+        }
+        if(delimiterPos < 0) {
+            return new String[] { reference, };
         }
-        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);
+        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, };
         }
-        return retval;
+        
+        String sheetName = partA.substring(0, plingPos + 1); // +1 to include delimiter
+        
+        return new String [] { partA, sheetName + partB, };
     }
 }
\ No newline at end of file
index def58472a8455f8577de38be6ddefe349ec56d05..47d579d94f8b509df44064c58247b2f3ecc4b90e 100644 (file)
    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);
-        }
-        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);
-        }
-        row = Integer.parseInt(ref)-1;
-    }
-
-    public CellReference(int pRow, int pCol) {
-        this(pRow,pCol,false,false);
+        _sheetName = parts[0];
+        String colRef = parts[1]; 
+        if (colRef.length() < 1) {
+            throw new IllegalArgumentException("Invalid Formula cell reference: '"+cellRef+"'");
+        }
+        _isColAbs = colRef.charAt(0) == '$';
+        if (_isColAbs) {
+            colRef=colRef.substring(1);
+        }
+        _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 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;}
-    
-    protected void setSheetName(String sheetName) {
-       this.sheetName = sheetName;
+    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 _rowIndex;}
+    public short getCol(){return (short) _colIndex;}
+    public boolean isRowAbsolute(){return _isRowAbs;}
+    public boolean isColAbsolute(){return _isColAbs;}
+    /**
+      * @return possibly <code>null</code> if this is a 2D reference.  Special characters are not
+      * escaped or delimited
+      */
+    public String getSheetName(){
+        return _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<lastQuotePos; i++) { // Note boundaries - skip outer quotes
+            char ch = reference.charAt(i);
+            if(ch != SPECIAL_NAME_DELIMITER) {
+                sb.append(ch);
+                continue;
+            }
+            if(i < lastQuotePos) {
+                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
+                    sb.append(ch);
+                    continue;
+                }
+            }
+            throw new RuntimeException("Bad sheet name quote escaping: (" + reference + ")");
+        }
+        return sb.toString();
     }
 
     /**
-     * takes in a 0-based base-10 column and returns a ALPHA-26 representation
+     * Takes in a 0-based base-10 column and returns a ALPHA-26
+     *  representation.
+     * eg column #3 -> 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:
+     *    <table border="0" cellpadding="1" cellspacing="0" summary="Example return values">
+     *      <tr><th align='left'>Result</th><th align='left'>Comment</th></tr>
+     *      <tr><td>A1</td><td>Cell reference without sheet</td></tr>
+     *      <tr><td>Sheet1!A1</td><td>Standard sheet name</td></tr>
+     *      <tr><td>'O''Brien''s Sales'!A1'&nbsp;</td><td>Sheet name with special characters</td></tr>
+     *    </table>
+     * @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);
     }
 }
index 771db767be07312671b2d8d6e0f07bc2055ba2c1..ef9acfe60b7cd669b1bdeeb50d49a2f901a442a4 100644 (file)
@@ -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 <tt>InputStream</tt>.  Normally the stream is read until
+     * EOF.  The stream is always closed.<p/>
+     * 
+     * Some streams are usable after reaching EOF (typically those that return <code>true</code> 
+     * for <tt>markSupported()</tt>).  In the unlikely case that the caller has such a stream 
+     * <i>and</i> needs to use it after this constructor completes, a work around is to wrap the
+     * stream in order to trap the <tt>close()</tt> call.  A convenience method (
+     * <tt>createNonClosingInputStream()</tt>) has been provided for this purpose:
+     * <pre>
+     * InputStream wrappedStream = POIFSFileSystem.createNonClosingInputStream(is);
+     * HSSFWorkbook wb = new HSSFWorkbook(wrappedStream);
+     * is.reset(); 
+     * doSomethingElse(is); 
+     * </pre>
+     * Note also the special case of <tt>ByteArrayInputStream</tt> for which the <tt>close()</tt>
+     * method does nothing. 
+     * <pre>
+     * ByteArrayInputStream bais = ...
+     * HSSFWorkbook wb = new HSSFWorkbook(bais); // calls bais.close() !
+     * bais.reset(); // no problem
+     * doSomethingElse(bais);
+     * </pre>
      *
      * @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 <code>false</code> 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);
     }
 
     /**
index d554298400e76e398cf5c070200490fc95bb48a9..472fd8b8b59f31ceb5554ffbf06fac99b4de29a9 100644 (file)
@@ -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;
index b883d71b186acc8a56bcfa435c398d507e0561fc..2278649cb89cf4e73fd4dcce2c773ed390043a49 100644 (file)
@@ -245,6 +245,16 @@ public class LittleEndian
         putNumber(data, offset, value, SHORT_SIZE);
     }
 
+    /**
+     * executes:<p/>
+     * <code>
+     * data[offset] = (byte)value;
+     * </code></p>
+     * 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
index 5bc0d3e9a96f2d7891037d08d0c5364247f9abc7..6562263d5474b8acda1fc5322e707fedc3c2ce10 100644 (file)
@@ -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() {
index 179698dc8dcd83bfccccc89831c5346a9fc48ef3..4b9a64c1c0e0d78212d356b25b6dc552d9dd3154 100644 (file)
 * 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 &lt; amolweb at ya hoo dot com &gt;
  *   
  */
-public class Area2DEval implements AreaEval {
-
-    private AreaPtg delegate;
+public final class Area2DEval implements AreaEval {
+// TODO -refactor with Area3DEval
+    private final 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();
     }
 }
index d371a0985502fdf8c19bce52304deb4f4e0f1f17..2f539142d154e48677b40a836c79072b354c38f9 100644 (file)
 * 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 &lt; amolweb at ya hoo dot com &gt;
  *  
  */
-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();
+    }
 }
index f60d63c131f50e30cd0ead767d7f1e983f469af1..82cc8a9b40d8957de83346b27e5d4bf57fcef317 100644 (file)
@@ -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
index 6a04068b76d203a9718b310478a396ed8cd502df..7b625aaa98b8970f930554d616b64d2167cb3d53 100644 (file)
@@ -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:<br/>
+     * <code>(b ? BoolEval.TRUE : BoolEval.FALSE)</code>
+     * @return a <tt>BoolEval</tt> instance representing <tt>b</tt>.
+     */
+    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();
+    }
 }
index 2d8c58ef3ed5a5182778ec4cfb3a73c665bb9aa7..e54cd483f1972238d06d5fd74be2135be449c1f9 100644 (file)
 * 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 &lt; amolweb at ya hoo dot com &gt;
  *  
  */
-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() {
index 6dd3db23de6af53ab1707900acf18e18298a1883..021168ad7907edbd771dfb5a367893cf36c8aa2b 100644 (file)
 * 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 &lt; amolweb at ya hoo dot com &gt;
  *  
  */
-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) 
index 43ef6c5127c975516b91633f4bb491c65ecea4f5..e8e197d201588769ca68f6006ff4d1f7ec26d9ad 100644 (file)
 * 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 &lt; amolweb at ya hoo dot com &gt;
- * 
+ *
  */
-public class ErrorEval implements ValueEval {
+public final class ErrorEval implements ValueEval {
 
-    private int errorCode;
+    // convenient access to namespace
+    private static final HSSFErrorConstants EC = null;
 
+    /** <b>#NULL!</b>  - Intersection of two cell ranges is empty */
+    public static final ErrorEval NULL_INTERSECTION = new ErrorEval(EC.ERROR_NULL);
+    /** <b>#DIV/0!</b> - Division by zero */
+    public static final ErrorEval DIV_ZERO = new ErrorEval(EC.ERROR_DIV_0);
+    /** <b>#VALUE!</b> - Wrong type of operand */
+    public static final ErrorEval VALUE_INVALID = new ErrorEval(EC.ERROR_VALUE);
+    /** <b>#REF!</b> - Illegal or deleted cell reference */
+    public static final ErrorEval REF_INVALID = new ErrorEval(EC.ERROR_REF);
+    /** <b>#NAME?</b> - Wrong function or range name */
+    public static final ErrorEval NAME_INVALID = new ErrorEval(EC.ERROR_NAME);
+    /** <b>#NUM!</b> - Value range overflow */
+    public static final ErrorEval NUM_ERROR = new ErrorEval(EC.ERROR_NUM);
+    /** <b>#N/A</b> - Argument or function not available */
+    public static final ErrorEval NA = new ErrorEval(EC.ERROR_NA);
 
-    public static final ErrorEval NAME_INVALID = new ErrorEval(525);
 
-    public static final ErrorEval VALUE_INVALID = new ErrorEval(519);
+    // POI internal error codes
+    private static final int CIRCULAR_REF_ERROR_CODE = 0xFFFFFFC4;
+    private static final int FUNCTION_NOT_IMPLEMENTED_CODE = 0xFFFFFFE2;
 
-    
-    // Non std error codes
-    public static final ErrorEval UNKNOWN_ERROR = new ErrorEval(-20);
+    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);
 
-    public static final ErrorEval FUNCTION_NOT_IMPLEMENTED = new ErrorEval(-30);
 
-    public static final ErrorEval REF_INVALID = new ErrorEval(-40);
+    /**
+     * 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 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 (executable)
index 0000000..7a23901
--- /dev/null
@@ -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 <i>within</i> operator and function
+ * implementations.   Note - <tt>OperationEval.evaluate()</tt> and <tt>Function.evaluate()</tt>
+ * method signatures do not throw this exception so it cannot propagate outside.<p/>
+ * 
+ * Here is an example coded without <tt>EvaluationException</tt>, to show how it can help:
+ * <pre>
+ * 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();
+ *     }
+ *     // ...
+ * }    
+ * </pre>
+ * 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.<br/>
+ * Using <tt>EvaluationException</tt> allows the error returning code to be consolidated to one
+ * place.<p/>
+ * <pre>
+ * 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;
+ *}
+ * </pre>   
+ * It is not mandatory to use EvaluationException, doing so might give the following advantages:<br/>
+ *  - Methods can more easily be extracted, allowing for re-use.<br/>
+ *  - Type management (typecasting etc) is simpler because error conditions have been separated from
+ * intermediate calculation values.<br/>
+ *  - Fewer local variables are required. Local variables can have stronger types.<br/>
+ *  - 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.<p/>
+ *  
+ * <b>Note</b> - Only standard evaluation errors are represented by <tt>EvaluationException</tt> (
+ * 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
+
+    /** <b>#VALUE!</b> - Wrong type of operand */
+       public static EvaluationException invalidValue() {
+               return new EvaluationException(ErrorEval.VALUE_INVALID);
+       }
+    /** <b>#REF!</b> - Illegal or deleted cell reference */
+       public static EvaluationException invalidRef() {
+               return new EvaluationException(ErrorEval.REF_INVALID);
+       }
+    /** <b>#NUM!</b> - 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 (executable)
index 0000000..b1d81e6
--- /dev/null
@@ -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 
+ * <tt>AbstractFunctionPtg.field_2_fnc_index</tt> == 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);
+       }
+
+}
index d1420b2e852ed7c9d955589521d63f0ed1366ad5..533c604a0c77fec760dd5cb033825d2a8d9402af 100644 (file)
@@ -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
index ecada85d562b8f4048cd1c28bc07727c0966d968..22d87b7e4d4f2dccec54eb8680c450127f9e9513 100644 (file)
 * 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 &lt; amolweb at ya hoo dot com &gt;
  *  
  */
-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();
+        }
+        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;
         }
-        return retval;
+        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 (executable)
index 0000000..682394b
--- /dev/null
@@ -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 (executable)
index 0000000..be1cda5
--- /dev/null
@@ -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 <tt>NumberEval</tt>, <tt>StringEval</tt>, <tt>BoolEval</tt> or <tt>BlankEval</tt>.
+        * Never <code>null</code> or <tt>ErrorEval</tt>.
+        * @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.
+        * 
+        *    <table border="1" cellpadding="1" cellspacing="1" summary="sample spreadsheet">
+        *      <tr><th>&nbsp;</th><th>&nbsp;A&nbsp;</th><th>&nbsp;B&nbsp;</th><th>&nbsp;C&nbsp;</th><th>&nbsp;D&nbsp;</th></tr>
+        *      <tr><th>1</th><td>15</td><td>20</td><td>25</td><td>&nbsp;</td></tr>
+        *      <tr><th>2</th><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>200</td></tr>
+        *      <tr><th>3</th><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>300</td></tr>
+        *      <tr><th>3</th><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>400</td></tr>
+        *    </table>
+        * 
+        * If the formula "=1000+A1:B1+D2:D3" is put into the 9 cells from A2 to C4, the spreadsheet
+        * will look like this:
+        * 
+        *    <table border="1" cellpadding="1" cellspacing="1" summary="sample spreadsheet">
+        *      <tr><th>&nbsp;</th><th>&nbsp;A&nbsp;</th><th>&nbsp;B&nbsp;</th><th>&nbsp;C&nbsp;</th><th>&nbsp;D&nbsp;</th></tr>
+        *      <tr><th>1</th><td>15</td><td>20</td><td>25</td><td>&nbsp;</td></tr>
+        *      <tr><th>2</th><td>1215</td><td>1220</td><td>#VALUE!</td><td>200</td></tr>
+        *      <tr><th>3</th><td>1315</td><td>1320</td><td>#VALUE!</td><td>300</td></tr>
+        *      <tr><th>4</th><td>#VALUE!</td><td>#VALUE!</td><td>#VALUE!</td><td>400</td></tr>
+        *    </table>
+        * 
+        * 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.<p/>
+        * 
+        * The same concept is extended to references across sheets, such that even multi-row, 
+        * multi-column areas can be useful.<p/>
+        * 
+        * Of course with carefully (or carelessly) chosen parameters, cyclic references can occur and
+        * hence this method <b>can</b> throw a 'circular reference' EvaluationException.  Note that 
+        * this method does not attempt to detect cycles.  Every cell in the specified Area <tt>ae</tt>
+        * has already been evaluated prior to this method call.  Any cell (or cell<b>s</b>) part of 
+        * <tt>ae</tt> that would incur a cyclic reference error if selected by this method, will 
+        * already have the value <t>ErrorEval.CIRCULAR_REF_ERROR</tt> 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 <code>null</code>. Never
+        *  <tt>ErrorEval</tt>.
+        * @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  <tt>ErrorEval</tt>, and <code>null</code> 
+        */
+       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.<br/>
+        * Value is first coerced to a <tt>double</tt> ( See <tt>coerceValueToDouble()</tt> ).<p/>
+        * 
+        * Excel typically converts doubles to integers by truncating toward negative infinity.<br/>
+        * The equivalent java code is:<br/>
+        * &nbsp;&nbsp;<code>return (int)Math.floor(d);</code><br/>
+        * <b>not</b>:<br/>
+        * &nbsp;&nbsp;<code>return (int)d; // wrong - rounds toward zero</code> 
+        * 
+        */
+       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 - <tt>BlankEval</tt> is not supported and must be handled by the caller. 
+        * @param ev must be a <tt>NumberEval</tt>, <tt>StringEval</tt> or <tt>BoolEval</tt>
+        * @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 <tt>parseDouble()</tt> for allowable formats).
+        * @throws RuntimeException if the supplied parameter is not <tt>NumberEval</tt>,
+        *  <tt>StringEval</tt> or <tt>BoolEval</tt>
+        */
+       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.<br/>
+        * Tolerates currency prefixes, commas, leading and trailing spaces.<p/>
+        *   
+        *  Some examples:<br/> 
+        *  " 123 " -&gt; 123.0<br/>
+        *  ".123" -&gt; 0.123<br/>
+        *  These not supported yet:<br/>
+        *  " $ 1,000.00 " -&gt; 1000.0<br/>
+        *  "$1.25E4" -&gt; 12500.0<br/>
+        *  "5**2" -&gt; 500<br/>
+        *  "250%" -&gt; 2.5<br/>
+        *  
+        * @param text
+        * @return <code>null</code> 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 <tt>NumberEval</tt>, <tt>StringEval</tt>, <tt>BoolEval</tt>, or <tt>BlankEval</tt>
+        * @return the converted string value. never <code>null</code>
+        */
+       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() + ")");
+       }
+}
index 437c24e40dac7954f13489f163ec7b9b5de0a206..651c5d2aa2c829b87984b7f25fc0ef227b6e960c 100644 (file)
 * 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 &lt; amolweb at ya hoo dot com &gt;
  *  
  */
-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();
+        }
+        else if (ve instanceof BlankEval) {
+            // do nothing
+        }
+        else {
+            return ErrorEval.VALUE_INVALID;
         }
-        return retval;
+        
+        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() {
index 7b24cb062b86e03241946eb2281e3c0e6bc57b29..898d7a86186640d7c60b06dffd61d50ba1dd369d 100644 (file)
 * 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;
-    }
-
 }
index acedbe766d697a987683de7ac68c4198c6d1524c..622d686329623331901803e537a3c45bc61c62a1 100644 (file)
 * 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 {
-
-    private ValueEval value;
-
-    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 final class Ref3DEval implements RefEval {
+
+    private final ValueEval value;
+    private final Ref3DPtg delegate;
+
+    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();
     }
-
 }
index bb72adc4a05775fc9d687b414c2451a357db2cd6..e462586d72480e1c3cdc31482fe05428e4028e0a 100644 (file)
 * 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();
 }
index 01af4e8436611b49ccf83a7863834ece594a6899..27a9c6a62720b92ae56e90a80b9cfe74fce8956d 100644 (file)
 * 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 &lt; amolweb at ya hoo dot com &gt;
  *  
  */
-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();
+    }
 }
index b692f01ea2da2eabb4cd4b622c885e6f7c51a0ed..46c12236b9b3b51a446d4b93bad65a73477efaa8 100644 (file)
 * 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 <code>null</code>, possibly empty string.
+     */
+    String getStringValue();
 }
index 4bd77029f73fe2b27de6893cbee11a73a8ee0743..85a3845299a49686b8308b5832a601875d675cf7 100644 (file)
 * 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 &lt; amolweb at ya hoo dot com &gt;
  *  
  */
-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 
index b4975eefcf947e658ac923a14031b25974cd234a..ef6f533ea52d3afff1c69ad0549ffffea3662dc5 100644 (file)
 * 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 &lt; amolweb at ya hoo dot com &gt;
  *  
  */
-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();
         }
-        
-        if (retval == null) {
-            retval = new NumberEval(-d);
+        else if (ve instanceof BlankEval) {
+            // do nothing
         }
-
-        return retval;
+        else if (ve instanceof ErrorEval) {
+            return ve;
+        }
+        
+        return new NumberEval(-d);
     }
 
     public int getNumberOfOperands() {
index 847aa56fa6cbda27afa8bd25d44a4e5b0f03e653..edcc7bee790a84cd7dc88a73cdb7fc75a256ba4c 100644 (file)
 * 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 &lt; amolweb at ya hoo dot com &gt;
  *  
  */
-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();
     }
-
 }
index 5ffa2faeef73515f3bbe56faa6f8a59bcc49937a..1abcf34d2b90105392de7543390705e4d2a9a836 100644 (file)
@@ -24,7 +24,7 @@ package org.apache.poi.hssf.record.formula.eval;
  * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
  *
  */
-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);
-        }
-        
-        // erroreval
-        else if (eval instanceof ErrorEval) {
-            retval = eval;
-        }
-        
-        // refeval
-        else if (eval instanceof RefEval) {
-            RefEval re = (RefEval) eval;
-            retval = xlateRefEval(re);
+        if (eval instanceof StringEval) {
+            return xlateRefStringEval((StringEval) eval);
         }
         
-        else if (eval instanceof BlankEval) {
-            retval = xlateBlankEval(reval.isEvaluated() ? EVALUATED_REF_BLANK_IS_PARSED : REF_BLANK_IS_PARSED);
+        if (eval instanceof ErrorEval) {
+            return eval;
         }
         
-        // probably AreaEval ? then not acceptable.
-        else { 
-            throw new RuntimeException("Invalid ValueEval type passed for conversion: " + eval.getClass());
+        if (eval instanceof BlankEval) {
+            return xlateBlankEval(REF_BLANK_IS_PARSED);
         }
-
         
-        
-
-        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;
     }
-    
 }
index 592402b80d20c60e846615f3162a8c5a3c907019..bf888b97df41d76bca89213d2e5eb66652ec737f 100644 (file)
@@ -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
                 ));
     
     /**
index 3491109177e8c50e11f9d4a14da8953e7aa8ce36..4043040713164d77e93d1127e1cfe3b5254ab0ce 100644 (file)
@@ -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
                 ));
     
     /**
index 8eb7e841d6709625af2728caed8eefe5477944b5..c054c6dac4dcc5b6839eab4fc980b89015dc6809 100644 (file)
 * 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 (executable)
index 0000000..56d2855
--- /dev/null
@@ -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.<br/>
+ * Two important functions with this feature are <b>INDIRECT</b> and <b>OFFSET</b><p/>
+ *  
+ * In POI, the <tt>HSSFFormulaEvaluator</tt> 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 <tt>evaluate()</tt> method on the common
+ * interface <tt>Function</tt> does not take a workbook parameter.<p>  
+ * 
+ * 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 <code>null</code>,
+        *                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 <code>null</code>. Possibly an instance of <tt>ErrorEval</tt> 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);
+}
index 8bac3d0c02029f1c2c4eeaca1354c7506eb48db3..40ed1da4903ab12507c2a8e69ffe52522a20dedb 100644 (file)
-/*
-* 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.<p/>
+ * 
+ * HLOOKUP finds a column in a lookup table by the first row value and returns the value from another row.
+ * 
+ * <b>Syntax</b>:<br/>
+ * <b>HLOOKUP</b>(<b>lookup_value</b>, <b>table_array</b>, <b>row_index_num</b>, range_lookup)<p/>
+ * 
+ * <b>lookup_value</b>  The value to be found in the first column of the table array.<br/>
+ * <b>table_array</> An area reference for the lookup data. <br/>
+ * <b>row_index_num</b> a 1 based index specifying which row value of the lookup data will be returned.<br/>
+ * <b>range_lookup</b> If TRUE (default), HLOOKUP finds the largest value less than or equal to 
+ * the lookup_value.  If FALSE, only exact matches will be considered<br/>   
+ * 
+ * @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 <tt>AreaEval</tt>
+        * 
+        * @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);
+       }
 }
index 90dbd591b75d33b58900fd1a6ce1a840d4775540..7aba5db72af837f8b7b355927669c6cf13553e19 100644 (file)
@@ -28,28 +28,22 @@ import org.apache.poi.hssf.record.formula.eval.Eval;
  * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
  * 
  */
-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];
-            }
-            else {
-                retval = evalWhenFalse;
+                return args[1];
             }
-            break;
+            return evalWhenFalse;
         default:
-            retval = ErrorEval.UNKNOWN_ERROR;
+            return ErrorEval.VALUE_INVALID;
         }
-        return retval;
     }
-
-
 }
index c7464ffed804f49fbbea0a837d753df162b7bd5a..935e7cdbbdaebb968132013efa276563a4373a90 100644 (file)
 * 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<p/>
+ * 
+ * INDIRECT() returns the cell or area reference denoted by the text argument.<p/> 
+ * 
+ * <b>Syntax</b>:</br>
+ * <b>INDIRECT</b>(<b>ref_text</b>,isA1Style)<p/>
+ * 
+ * <b>ref_text</b> a string representation of the desired reference as it would normally be written
+ * in a cell formula.<br/>
+ * <b>isA1Style</b> (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;
+       }
 
 }
index 6e8f84b34260c59c5dab611484dfaafdee35836d..c0e482e5a8e70de1688531003318449a88129597 100644 (file)
 * 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 &lt; amolweb at ya hoo dot com &gt;
  *
  */
-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);
+       }
 }
index c0cb39b268369a6b9a9bc7fdbf09121cf4fc3bca..0bc49b40707436fe04dd82cd904e5588c0eeef55 100644 (file)
 * 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 &lt; amolweb at ya hoo dot com &gt;
  *
  */
-public class Len extends TextFunction {
-
+public final class Len extends TextFunction {
 
-    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;
-    }
+       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);
 
-    
-    /**
-     * 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();
+               }
+       }
 }
index f98ccca7e3a02826a34c3f930be9ed9d9c96950d..be1d0d0f940f47a32d55a69cd43231d0fcb1a297 100644 (file)
@@ -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.<p/>
+ * 
+ * LOOKUP finds an index  row in a lookup table by the first column value and returns the value from another column.
+ * 
+ * <b>Syntax</b>:<br/>
+ * <b>VLOOKUP</b>(<b>lookup_value</b>, <b>lookup_vector</b>, result_vector)<p/>
+ * 
+ * <b>lookup_value</b>  The value to be found in the lookup vector.<br/>
+ * <b>lookup_vector</> An area reference for the lookup data. <br/>
+ * <b>result_vector</b> Single row or single column area reference from which the result value is chosen.<br/>
+ * 
+ * @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 (file)
index 0000000..d6a8489
--- /dev/null
@@ -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 <tt>AreaEval</tt>.
+        */
+       public interface ValueVector {
+               ValueEval getItem(int index);
+               int getSize();
+       }
+       /**
+        * Enumeration to support <b>4</b> valued comparison results.<p/>
+        * 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.<p/>
+        * 
+        * A simple int might have done the same job, but there is risk in confusion with the well 
+        * known <tt>Comparable.compareTo()</tt> and <tt>Comparator.compare()</tt> 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 <tt>CompareResult</tt>: <tt>LESS_THAN</tt>, <tt>EQUAL</tt>, 
+                * <tt>GREATER_THAN</tt> or <tt>TYPE_MISMATCH</tt>
+                */
+               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 (<b>col_index_num</b> 
+        * or <b>row_index_num</b> respectively).<br>
+        * Sample behaviour:
+        *    <table border="0" cellpadding="1" cellspacing="2" summary="Sample behaviour">
+        *      <tr><th>Input&nbsp;&nbsp;&nbsp;Return</th><th>Value&nbsp;&nbsp;</th><th>Thrown Error</th></tr>
+        *      <tr><td>5</td><td>4</td><td>&nbsp;</td></tr>
+        *      <tr><td>2.9</td><td>2</td><td>&nbsp;</td></tr>
+        *      <tr><td>"5"</td><td>4</td><td>&nbsp;</td></tr>
+        *      <tr><td>"2.18e1"</td><td>21</td><td>&nbsp;</td></tr>
+        *      <tr><td>"-$2"</td><td>-3</td><td>*</td></tr>
+        *      <tr><td>FALSE</td><td>-1</td><td>*</td></tr>
+        *      <tr><td>TRUE</td><td>0</td><td>&nbsp;</td></tr>
+        *      <tr><td>"TRUE"</td><td>&nbsp;</td><td>#REF!</td></tr>
+        *      <tr><td>"abc"</td><td>&nbsp;</td><td>#REF!</td></tr>
+        *      <tr><td>""</td><td>&nbsp;</td><td>#REF!</td></tr>
+        *      <tr><td>&lt;blank&gt;</td><td>&nbsp;</td><td>#VALUE!</td></tr>
+        *    </table><br/>
+        *    
+        *  * 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 (<b>range_lookup</b>) 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<vector[newMid]),
+                       // this execution path only moves highIx it down as far as newMid, not midIx,
+                       // which would be more efficient.
+                       bsi.narrowSearch(newMid, cr.isLessThan());
+                       return -1;
+               }
+       }
+       /**
+        * Once the binary search has found a single match, (V/H)LOOKUP steps one by one over subsequent
+        * values to choose the last matching item.
+        */
+       private static int findLastIndexInRunOfEqualValues(LookupValueComparer lookupComparer, ValueVector vector,
+                               int firstFoundIndex, int maxIx) {
+               for(int i=firstFoundIndex+1; i<maxIx; i++) {
+                       if(!lookupComparer.compareTo(vector.getItem(i)).isEqual()) {
+                               return i-1;
+                       }
+               }
+               return maxIx - 1;
+       }
+
+       public static LookupValueComparer createLookupComparer(ValueEval lookupValue) throws EvaluationException {
+               
+               if (lookupValue instanceof BlankEval) {
+                       // blank eval can never be found in a lookup array 
+                       throw new EvaluationException(ErrorEval.NA);
+               }
+               if (lookupValue instanceof StringEval) {
+                       return new StringLookupComparer((StringEval) lookupValue);
+               }
+               if (lookupValue instanceof NumberEval) {
+                       return new NumberLookupComparer((NumberEval) lookupValue);
+               }
+               if (lookupValue instanceof BoolEval) {
+                       return new BooleanLookupComparer((BoolEval) lookupValue);
+               }
+               throw new IllegalArgumentException("Bad lookup value type (" + lookupValue.getClass().getName() + ")");
+       }
+}
index 74afaf695c3d3ce011173bebdb772afc70782ea1..a2a12cdba8cf656144a2c2d72b36ffc47364d040 100644 (file)
 * 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 Match 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.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;
+import org.apache.poi.hssf.record.formula.functions.LookupUtils.CompareResult;
+import org.apache.poi.hssf.record.formula.functions.LookupUtils.LookupValueComparer;
+
+/**
+ * Implementation for the MATCH() Excel function.<p/>
+ * 
+ * <b>Syntax:</b><br/>
+ * <b>MATCH</b>(<b>lookup_value</b>, <b>lookup_array</b>, match_type)<p/>
+ * 
+ * Returns a 1-based index specifying at what position in the <b>lookup_array</b> the specified 
+ * <b>lookup_value</b> is found.<p/>
+ * 
+ * Specific matching behaviour can be modified with the optional <b>match_type</b> parameter.
+ * 
+ *    <table border="0" cellpadding="1" cellspacing="0" summary="match_type parameter description">
+ *      <tr><th>Value</th><th>Matching Behaviour</th></tr>
+ *      <tr><td>1</td><td>(default) find the largest value that is less than or equal to lookup_value.
+ *        The lookup_array must be in ascending <i>order</i>*.</td></tr>
+ *      <tr><td>0</td><td>find the first value that is exactly equal to lookup_value.
+ *        The lookup_array can be in any order.</td></tr>
+ *      <tr><td>-1</td><td>find the smallest value that is greater than or equal to lookup_value.
+ *        The lookup_array must be in descending <i>order</i>*.</td></tr>
+ *    </table>
+ * 
+ * * Note regarding <i>order</i> - For the <b>match_type</b> 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.<br>
+ *  The (ascending) sort order expected by MATCH() is:<br/>
+ *  numbers (low to high), strings (A to Z), boolean (FALSE to TRUE)<br/>
+ *  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<lookupRange.length; i++) {
+                       CompareResult cmp = lookupComparer.compareTo(lookupRange[i]);
+                       if(cmp.isEqual()) {
+                               return i;
+                       }
+                       if(cmp.isGreaterThan()) {
+                               if(i<1) {
+                                       throw new EvaluationException(ErrorEval.NA);
+                               }
+                               return i-1;
+                       }
+               }
+
+               throw new EvaluationException(ErrorEval.NA);
+       }
+
+       private static LookupValueComparer createLookupComparer(ValueEval lookupValue, boolean matchExact) throws EvaluationException {
+               if (matchExact && lookupValue instanceof StringEval) {
+                       String stringValue = ((StringEval) lookupValue).getStringValue();
+                       if(isLookupValueWild(stringValue)) {
+                               throw new RuntimeException("Wildcard lookup values '" + stringValue + "' not supported yet");
+                       }
+                       
+               }
+               return LookupUtils.createLookupComparer(lookupValue);
+       }
 
+       private static boolean isLookupValueWild(String stringValue) {
+               if(stringValue.indexOf('?') >=0 || stringValue.indexOf('*') >=0) {
+                       return true;
+               }
+               return false;
+       }
 }
index 21e30de0c27fd757571c15eb991371a90cc17701..e25db7b746724b9f4015f9a86eaa57a932153556 100644 (file)
 * 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 &lt; amolweb at ya hoo dot com &gt; 
  *
  */
-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  
index d6c4399ae33ac9b0dfba899edf0aff61c6995ae1..7f30aa4cec62a327e0ff3de83e5e978b2428db5a 100644 (file)
@@ -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<br/> MID returns a specific number of
+ * characters from a text string, starting at the specified position.<p/>
+ * 
+ * <b>Syntax<b>:<br/> <b>MID</b>(<b>text</b>, <b>start_num</b>,
+ * <b>num_chars</b>)<br/>
+ * 
  * @author Manda Wilson &lt; wilson at c bio dot msk cc dot org &gt;
  */
-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
index a998a870f676ecab3843ef668d531e283cb04bc9..21ba47b569c552378cc7b3297a19db3678f64d3f 100644 (file)
 * 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  
index 2840c898836b83e3b2cf964c15362ac320370b56..0e7cce217e2ebc04e07c99e891ade018e83afb6e 100644 (file)
 * 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;
+
+        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 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  
-                ));
+        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 <code>null</code>.
      * 
      * @param operands
      * @param srcRow
      * @param srcCol
      */
     protected double[] getNumberArray(Eval[] operands, int srcRow, short srcCol) {
-        double[] retval = new double[30];
-        int count = 0;
+        if (operands.length > getMaxNumOperands()) {
+            return null;
+        }
+        DoubleList retval = new DoubleList();
         
-        outer: do { // goto simulator loop
-            if (operands.length > getMaxNumOperands()) {
-                break outer;
+        for (int i=0, iSize=operands.length; i<iSize; i++) {
+            double[] temp = getNumberArray(operands[i], srcRow, srcCol);
+            if (temp == null) {
+                return null; // error occurred.
             }
-            else {
-                for (int i=0, iSize=operands.length; i<iSize; i++) {
-                    double[] temp = getNumberArray(operands[i], srcRow, srcCol);
-                    if (temp == null) {
-                        retval = null; // error occurred.
-                        break;
-                    }
-                    retval = putInArray(retval, count, temp);
-                    count += temp.length;
-                }
-            }
-        } while (false); // end goto simulator loop
-        
-        if (retval != null) {
-            double[] temp = retval;
-            retval = new double[count];
-            System.arraycopy(temp, 0, retval, 0, count);
+            retval.add(temp);
         }
-        
-        return retval;
+        return retval.toArray();
     }
     
     /**
@@ -120,13 +123,11 @@ public abstract class MultiOperandNumericFunction extends NumericFunction {
      * @param srcCol
      */
     protected double[] getNumberArray(Eval operand, int srcRow, short srcCol) {
-        double[] retval;
-        int count = 0;
         
         if (operand instanceof AreaEval) {
             AreaEval ae = (AreaEval) operand;
             ValueEval[] values = ae.getValues();
-            retval = new double[values.length];
+            DoubleList retval = new DoubleList();
             for (int j=0, jSize=values.length; j<jSize; j++) {
                 /*
                  * TODO: For an AreaEval, we are constructing a RefEval
@@ -136,98 +137,66 @@ public abstract class MultiOperandNumericFunction extends NumericFunction {
                  * HSSFFormulaEvaluator where we store an array
                  * of RefEvals as the "values" array. 
                  */
-                RefEval re = (values[j] instanceof RefEval)
-                        ? new Ref2DEval(null, ((RefEval) values[j]).getInnerValueEval(), true)
-                        : new Ref2DEval(null, values[j], false);
+                RefEval re = new Ref2DEval(null, values[j]);
                 ValueEval ve = singleOperandEvaluate(re, srcRow, srcCol);
                 
                 if (ve instanceof NumericValueEval) {
                     NumericValueEval nve = (NumericValueEval) ve;
-                    retval = putInArray(retval, count++, nve.getNumberValue());
+                    retval.add(nve.getNumberValue());
                 }
-                else if (ve instanceof BlankEval) {} // ignore operand
+                else if (ve instanceof BlankEval) {
+                    // note - blanks are ignored, so returned array will be smaller.
+                } 
                 else {
-                    retval = null; // null => indicate to calling subclass that error occurred
-                    break;
+                    return null; // indicate to calling subclass that error occurred
                 }
             }
+            return retval.toArray();
         }
-        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
-            }
-        }
         
-        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 <code>false</code> 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];
+    protected static final boolean areSubArraysConsistent(double[][] values) {
+        
+        if (values == null || values.length < 1) {
+            // TODO this doesn't seem right.  Fix or add comment.
+            return true;
         }
-        if (tarr.length != arr.length) {
-            System.arraycopy(tarr, 0, arr, 0, tarr.length);
+        
+        if (values[0] == null) {
+            return false;
         }
-        for (int i=0, iSize=d.length; i<iSize; i++) {
-            arr[pos+i] = d[i];
+        int outerMax = values.length;
+        int innerMax = values[0].length;
+        for (int i=1; i<outerMax; i++) { // note - 'i=1' start at second sub-array
+            double[] subArr = values[i];
+            if (subArr == null) {
+                return false;
+            }
+            if (innerMax != subArr.length) {
+                return false;
+            }
         }
-        return arr;
+        return true;
     }
     
-    protected static boolean areSubArraysConsistent(double[][] values) {
-        boolean retval = false;
-        
-        outer: do {
-            if (values != null && values.length > 0) {
-                if (values[0] == null)
-                    break outer;
-                int len = values[0].length;
-                for (int i=1, iSize=values.length; i<iSize; i++) {
-                    if (values[i] == null)
-                        break outer;
-                    int tlen = values[i].length;
-                    if (len != tlen) {
-                        break outer;
-                    }
-                }
-            }
-            retval = true;
-        } while (false);
-        return retval;
-    }
+   
     
 }
index 2ab1b8c4e82d24639c2711955b5302eb73daf0ed..eef0f451300b70d8513d5ee1fd0d196cefb63a3c 100644 (file)
 * 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 Na extends NotImplementedFunction {
+import org.apache.poi.hssf.record.formula.eval.ErrorEval;
+import org.apache.poi.hssf.record.formula.eval.Eval;
+
+/**
+ * Implementation of Excel function NA()
+ * 
+ * @author Josh Micich
+ */
+public final class Na implements Function {
+
+       public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
+               return ErrorEval.NA;
+       }
 
 }
index 7b43317a3b7296c6f9c1452b3282cd78d1f1c8ab..fd96f1495a9c47978102c43caf1ad250eca49e77 100644 (file)
@@ -39,10 +39,8 @@ public abstract class NumericFunction implements Function {
         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  
index 80539d15f94f9fdad5c181baa3bdd3deac403ef9..9497a5f21a908ae2baaed4d1cb81f57c19863340 100644 (file)
 * 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 Offset extends NotImplementedFunction {
+import org.apache.poi.hssf.record.formula.Area3DPtg;
+import org.apache.poi.hssf.record.formula.AreaPtg;
+import org.apache.poi.hssf.record.formula.eval.Area3DEval;
+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.NumericValueEval;
+import org.apache.poi.hssf.record.formula.eval.Ref3DEval;
+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 org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator;
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+/**
+ * Implementation for Excel function OFFSET()<p/>
+ * 
+ * OFFSET returns an area reference that is a specified number of rows and columns from a 
+ * reference cell or area.<p/>
+ * 
+ * <b>Syntax</b>:<br/>
+ * <b>OFFSET</b>(<b>reference</b>, <b>rows</b>, <b>cols</b>, height, width)<p/>
+ * <b>reference</b> is the base reference.<br/>
+ * <b>rows</b> is the number of rows up or down from the base reference.<br/>
+ * <b>cols</b> is the number of columns left or right from the base reference.<br/>
+ * <b>height</b> (default same height as base reference) is the row count for the returned area reference.<br/>
+ * <b>width</b> (default same width as base reference) is the column count for the returned area reference.<br/>
+ * 
+ * @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.<p/>
+                * 
+                * 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. <p/>
+                * 
+                * @param translationAmount may be zero negative or positive
+                * 
+                * @return the equivalent <tt>LinearOffsetRange</tt> 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);
+       }
 }
index ab446c9f2d4d4a4d2470e6ee077fd589e44a81a2..13522294fd4dceb665b086c0005ceb3d4f64fbd3 100644 (file)
@@ -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();
index 3d8cc1ae344d39011ffa51bc09d6949727356862..4dae76d981008a4be63fff017689db9fbf4670c0 100644 (file)
@@ -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();
index 6a4eb8edb7b45f46149c1c805e38a3641dc01bdc..aabffab2f6a8867d5e2c07555d021670440c5ad2 100644 (file)
@@ -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
  */
index fef7e03464c6ef04dc022f6b150bfc33960d770f..7995e66c34db1a99c439eb0b3bf14fd312b5f9bb 100644 (file)
@@ -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
                 ));
     
     /**
index 12fa5d7bd5f302743f3176a3505ba5c2af9c1eb3..9f6eafa4dcf958c85e2c31c923ace1d4cfc23e25 100644 (file)
 * 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 &lt; amolweb at ya hoo dot com &gt; 
- *
+ * Implementation for the Excel function SUMPRODUCT<p/>
+ * 
+ * Syntax : <br/>
+ *  SUMPRODUCT ( array1[, array2[, array3[, ...]]])
+ *    <table border="0" cellpadding="1" cellspacing="0" summary="Parameter descriptions">
+ *      <tr><th>array1, ... arrayN&nbsp;&nbsp;</th><td>typically area references, 
+ *      possibly cell references or scalar values</td></tr>
+ *    </table><br/>
+ *    
+ * Let A<b>n</b><sub>(<b>i</b>,<b>j</b>)</sub> represent the element in the <b>i</b>th row <b>j</b>th column 
+ * of the <b>n</b>th array<br/>   
+ * Assuming each array has the same dimensions (W, H), the result is defined as:<br/>    
+ * SUMPRODUCT = &Sigma;<sub><b>i</b>: 1..H</sub> &nbsp;
+ *     (&nbsp; &Sigma;<sub><b>j</b>: 1..W</sub> &nbsp;
+ *       (&nbsp; &Pi;<sub><b>n</b>: 1..N</sub> 
+ *                     A<b>n</b><sub>(<b>i</b>,<b>j</b>)</sub>&nbsp;
+ *    )&nbsp;
+ *  ) 
+ * 
+ * @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; n<maxN; n++) {
+                       double val = getScalarValue(evalArgs[n]);
+                       term *= val;
+               }
+               return new NumberEval(term);
+       }
+
+       private double getScalarValue(Eval arg) throws EvalEx {
+               
+               Eval eval;
+               if (arg instanceof RefEval) {
+                       RefEval re = (RefEval) arg;
+                       eval = re.getInnerValueEval();
+               } else {
+                       eval = arg;
+               }
+               
+               if (eval == null) {
+                       throw new RuntimeException("parameter may not be null");
+               }
+               if (eval instanceof AreaEval) {
+                       AreaEval ae = (AreaEval) eval;
+                       // an area ref can work as a scalar value if it is 1x1
+                       if(!ae.isColumn() || !ae.isRow()) {
+                               throw new EvalEx(ErrorEval.VALUE_INVALID);
+                       }
+                       eval = ae.getValues()[0];
+               }
+
+               if (!(eval instanceof ValueEval)) {
+                       throw new RuntimeException("Unexpected value eval class (" 
+                                       + eval.getClass().getName() + ")");
+               }
+               
+               return getProductTerm((ValueEval) eval, true);
+       }
+
+       private Eval evaluateAreaSumProduct(Eval[] evalArgs) throws EvalEx {
+               int maxN = evalArgs.length;
+               AreaEval[] args = new AreaEval[maxN];
+               try {
+                       System.arraycopy(evalArgs, 0, args, 0, maxN);
+               } catch (ArrayStoreException e) {
+                       // one of the other args was not an AreaRef
+                       return ErrorEval.VALUE_INVALID;
+               }
+
+               
+               AreaEval firstArg = args[0];
+               
+               int height = firstArg.getLastRow() - firstArg.getFirstRow() + 1;
+               int width = firstArg.getLastColumn() - firstArg.getFirstColumn() + 1; // TODO - junit
+               
+               
+
+               double[][][] elements = new double[maxN][][];
+               
+               for (int n = 0; n < maxN; n++) {
+                       elements[n] = evaluateArea(args[n], height, width);
+               }
+               double acc = 0;
+               
+               for(int r=0; r<height; r++) {
+                       for(int c=0; c<width; c++) {
+                               double term = 1D;
+                               for(int n=0; n<maxN; n++) {
+                                       term *= elements[n][r][c];
+                               }
+                               acc += term;
+                       }
+               }
+               
+               return new NumberEval(acc);
+       }
+
+       /**
+        * @return a 2-D array of the specified height and width corresponding to the evaluated cell 
+        *  values of the specified areaEval 
+        * @throws EvalEx if any ErrorEval value was encountered while evaluating the area
+        */
+       private static double[][] evaluateArea(AreaEval areaEval, int height, int width) throws EvalEx {
+               int fr =areaEval.getFirstRow();
+               int fc =areaEval.getFirstColumn();
+               
+               // check that height and width match
+               if(areaEval.getLastRow() - fr + 1 != height) {
+                       throw new EvalEx(ErrorEval.VALUE_INVALID);
+               }
+               if(areaEval.getLastColumn() - fc + 1 != width) {
+                       throw new EvalEx(ErrorEval.VALUE_INVALID);
+               }
+               ValueEval[] values = areaEval.getValues();
+               double[][] result = new double[height][width];
+               for(int r=0; r<height; r++) {
+                       for(int c=0; c<width; c++) {
+                               ValueEval ve = values[r*width + c];
+                               result[r][c] = getProductTerm(ve, false);
+                       }
+               }
+               return result;
+       }
+
+       /**
+        * Determines a <code>double</code> value for the specified <code>ValueEval</code>. 
+        * @param isScalarProduct <code>false</code> for SUMPRODUCTs over area refs.
+        * @throws EvalEx if <code>ve</code> represents an error value.
+        * <p/>
+        * Note - string values and empty cells are interpreted differently depending on 
+        * <code>isScalarProduct</code>.  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() + ")");
+       }
 }
index f4e1959be611737ae4be63240f84da03ec360f47..b74b4161ac2a32a1ac113d8c68fdc14d9e792285 100644 (file)
@@ -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() {
index 8e31224078cabd8d6258cc12a54965632e3c201d..30ad5ec2303ee70347d5da2cd219d97a2bc17884 100644 (file)
 * 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;
+package org.apache.poi.hssf.record.formula.functions;
 
 /**
+ * Implementation of Excel function SUMX2MY2()<p/>
+ * 
+ * Calculates the sum of differences of squares in two arrays of the same size.<br/>
+ * <b>Syntax</b>:<br/>
+ * <b>SUMX2MY2</b>(<b>arrayX</b>, <b>arrayY</b>)<p/>
+ * 
+ * result = &Sigma;<sub>i: 0..n</sub>(x<sub>i</sub><sup>2</sup>-y<sub>i</sub><sup>2</sup>)
+ * 
  * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt; 
- *
  */
-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);
     }
 }
index deb7675a4ef4467b5e9c14178729f3d579e7be6f..dfd730d12cd347b378de777bef046097f7888d94 100644 (file)
 * 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;
+package org.apache.poi.hssf.record.formula.functions;
 
 /**
+ * Implementation of Excel function SUMX2PY2()<p/>
+ * 
+ * Calculates the sum of squares in two arrays of the same size.<br/>
+ * <b>Syntax</b>:<br/>
+ * <b>SUMX2PY2</b>(<b>arrayX</b>, <b>arrayY</b>)<p/>
+ * 
+ * result = &Sigma;<sub>i: 0..n</sub>(x<sub>i</sub><sup>2</sup>+y<sub>i</sub><sup>2</sup>)
+ * 
  * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt; 
- *
  */
-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);
     }
 }
index c62a0b76221480acc2c5926ddc81d9983d4d7097..a1b2fec9b2854ebd5910db2e3d1bad7ca9b09f6a 100644 (file)
 * 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;
+package org.apache.poi.hssf.record.formula.functions;
 
 /**
+ * Implementation of Excel function SUMXMY2()<p/>
+ * 
+ * Calculates the sum of squares of differences between two arrays of the same size.<br/>
+ * <b>Syntax</b>:<br/>
+ * <b>SUMXMY2</b>(<b>arrayX</b>, <b>arrayY</b>)<p/>
+ * 
+ * result = &Sigma;<sub>i: 0..n</sub>(x<sub>i</sub>-y<sub>i</sub>)<sup>2</sup>
+ * 
  * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt; 
- *
  */
-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);
     }
 }
index 686c40b6278b3cadb94a862005e8ce542327f7be..b322cd985c32da0c59d3cc76e190e8a6bae55411 100644 (file)
@@ -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;
     }
 }
index 5e9d91c7cc7682eb40c6a5f0715d5697dd4c83d6..87e29ee34d7ba50ab63ab995dc2df4dd4610fd63 100644 (file)
 */
 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 &lt; wilson at c bio dot msk cc dot org &gt;
  */
-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();
+               }
+       }
 }
index ad8b88daf945de8399e2fbda95d115e4d26eedef..7d27491df1590a1089c13ec2750d41abad654fb5 100644 (file)
-/*
-* 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.<p/>
+ * 
+ * VLOOKUP finds a row in a lookup table by the first column value and returns the value from another column.
+ * 
+ * <b>Syntax</b>:<br/>
+ * <b>VLOOKUP</b>(<b>lookup_value</b>, <b>table_array</b>, <b>col_index_num</b>, range_lookup)<p/>
+ * 
+ * <b>lookup_value</b>  The value to be found in the first column of the table array.<br/>
+ * <b>table_array</> An area reference for the lookup data. <br/>
+ * <b>col_index_num</b> a 1 based index specifying which column value of the lookup data will be returned.<br/>
+ * <b>range_lookup</b> If TRUE (default), VLOOKUP finds the largest value less than or equal to 
+ * the lookup_value.  If FALSE, only exact matches will be considered<br/>   
+ * 
+ * @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 <tt>AreaEval</tt>
+        * 
+        * @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);
+       }
 }
index 1e6955ad9452fc996b2956ccda3bcfee49998003..b989c33a26790fdbdab30d815aaf7bcd614009f3 100644 (file)
 * 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 &lt; amolweb at ya hoo dot com &gt;
  *
  */
-public abstract class XYNumericFunction extends NumericFunction {
+public abstract class XYNumericFunction implements Function {
     protected static final int X = 0;
     protected static final int Y = 1;
+    
+    protected static final class DoubleArrayPair {
 
-    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  
-                ));
+               private final double[] _xArray;
+               private final double[] _yArray;
 
-    /**
-     * 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;
+               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<iSize; i++) {
-                Eval xEval = xops[i];
-                Eval yEval = yops[i];
-                
-                if (isNumberEval(xEval) && isNumberEval(yEval)) {
-                    retval[X] = ensureCapacity(retval[X], count);
-                    retval[Y] = ensureCapacity(retval[Y], count);
-                    retval[X][count] = getDoubleValue(xEval);
-                    retval[Y][count] = getDoubleValue(yEval);
-                    if (Double.isNaN(retval[X][count]) || Double.isNaN(retval[Y][count])) {
-                        retval = null;
-                        break;
-                    }
-                    count++;
-                }
-            }
-        }
-        
-        if (retval != null) {
-            double[][] temp = retval;
-            retval[X] = trimToSize(retval[X], count);
-            retval[Y] = trimToSize(retval[Y], count);
-        }
-        
-        return retval;
-    }
-    
-    protected double[][] getValues(Eval[] operands, int srcCellRow, short srcCellCol) {
-        double[][] retval = null;
-        
-        outer: do {
-            if (operands.length == 2) {
-                Eval[] xEvals = new Eval[1];
-                Eval[] yEvals = new Eval[1];
-                if (operands[X] instanceof AreaEval) {
-                    AreaEval ae = (AreaEval) operands[0];
-                    xEvals = ae.getValues();
-                }
-                else if (operands[X] instanceof ErrorEval) {
-                    break outer;
-                }
-                else {
-                    xEvals[0] = operands[X];
-                }
-                
-                if (operands[Y] instanceof AreaEval) {
-                    AreaEval ae = (AreaEval) operands[Y];
-                    yEvals = ae.getValues();
-                }
-                else if (operands[Y] instanceof ErrorEval) {
-                    break outer;
-                }
-                else {
-                    yEvals[0] = operands[Y];
-                }
-                
-                retval = getNumberArray(xEvals, yEvals, srcCellRow, srcCellCol);
-            }
-        } while (false);
+               for (int i=0, iSize=nArrayItems; i<iSize; i++) {
+                   Eval xEval = xops[i];
+                   Eval yEval = yops[i];
+                   
+                   if (isNumberEval(xEval) && isNumberEval(yEval)) {
+                       xResult[count] = getDoubleValue(xEval);
+                       yResult[count] = getDoubleValue(yEval);
+                       if (Double.isNaN(xResult[count]) || Double.isNaN(xResult[count])) {
+                           throw new EvaluationException(ErrorEval.NUM_ERROR);
+                       }
+                       count++;
+                   }
+               }
         
-        return retval;
+               return new double[][] {
+               trimToSize(xResult, count),
+            trimToSize(yResult, count),
+               };
     }
     
-
-    protected static double[] ensureCapacity(double[] arr, int pos) {
-        double[] temp = arr;
-        while (pos >= arr.length) {
-            arr = new double[arr.length << 2];
-        }
-        if (temp.length != arr.length)
-            System.arraycopy(temp, 0, arr, 0, temp.length);
-        return arr;
+    private static double[][] getValues(Eval argX, Eval argY) throws EvaluationException {
+       
+       if (argX instanceof ErrorEval) {
+                       throw new EvaluationException((ErrorEval) argX);
+               }
+       if (argY instanceof ErrorEval) {
+                       throw new EvaluationException((ErrorEval) argY);
+               }
+       
+        Eval[] xEvals;
+               Eval[] yEvals;
+               if (argX instanceof AreaEval) {
+                   AreaEval ae = (AreaEval) argX;
+                   xEvals = ae.getValues();
+               } else {
+                   xEvals = new Eval[] { argX, };
+               }
+               
+               if (argY instanceof AreaEval) {
+                   AreaEval ae = (AreaEval) argY;
+                   yEvals = ae.getValues();
+               } else {
+                   yEvals = new Eval[] { argY, };
+               }
+               
+               return getNumberArray(xEvals, yEvals);
     }
     
-    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 (executable)
index 0000000..90f5807
--- /dev/null
@@ -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 <tt>HSSFFormulaEvaluator.internalEvaluate()</tt>.
+ * 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.<br/>
+        */
+       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.<br/>
+        * 
+        * In the case of a <code>true</code> return code, the caller should
+        * continue evaluation of the specified cell, and also be sure to call
+        * <tt>endEvaluate()</tt> when complete.<br/>
+        * 
+        * In the case of a <code>false</code> return code, the caller should
+        * return an evaluation result of
+        * <tt>ErrorEval.CIRCULAR_REF_ERROR<tt>, and not call <tt>endEvaluate()</tt>.  
+        * <br/>
+        * @return <code>true</code> if the specified cell has not been visited yet in the current 
+        * evaluation. <code>false</code> 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. <p/>
+        * 
+        * Every successful call to <tt>startEvaluate</tt> must be followed by a
+        * call to <tt>endEvaluate</tt> (recommended in a finally block) to enable
+        * proper tracking of which cells are being evaluated at any point in time.<p/>
+        * 
+        * 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 (executable)
index 0000000..a06cd20
--- /dev/null
@@ -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 <tt>EvaluationCycleDetector</tt> instance available to
+ * each thread via a <tt>ThreadLocal</tt> in order to avoid adding a parameter
+ * to a few protected methods within <tt>HSSFFormulaEvaluator</tt>.
+ * 
+ * @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
+       }
+}
index f60a6adaaca3fcf8dce6b438a7c1f024ae5853fe..3fce3065579b1a31aed726021a308cfc65f061e9 100644 (file)
 * 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 &lt; amolweb at ya hoo dot com &gt;
  * 
- * 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();
-        }
-        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;
+        if (!stack.isEmpty()) {
+            throw new IllegalStateException("evaluation stack not empty");
+        }
+        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 <tt>NumberEval</tt>, <tt>StringEval</tt>, <tt>BoolEval</tt>,
+        *  <tt>BlankEval</tt> or <tt>ErrorEval</tt>. Never <code>null</code>.
+     */
+    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()) {
+
+        if (cell == null) {
+            return BlankEval.INSTANCE;
+        }
+        switch (cell.getCellType()) {
             case HSSFCell.CELL_TYPE_NUMERIC:
-                retval = new NumberEval(cell.getNumericCellValue());
-                break;
+                return new NumberEval(cell.getNumericCellValue());
             case HSSFCell.CELL_TYPE_STRING:
-                retval = new StringEval(cell.getRichStringCellValue().getString());
-                break;
+                return new StringEval(cell.getRichStringCellValue().getString());
             case HSSFCell.CELL_TYPE_FORMULA:
-                retval = internalEvaluate(cell, row, sheet, workbook);
-                break;
+                return internalEvaluate(cell, row, sheet, workbook);
             case HSSFCell.CELL_TYPE_BOOLEAN:
-                retval = cell.getBooleanCellValue() ? BoolEval.TRUE : BoolEval.FALSE;
-                break;
+                return BoolEval.valueOf(cell.getBooleanCellValue());
             case HSSFCell.CELL_TYPE_BLANK:
-                retval = BlankEval.INSTANCE;
-                break;
+                return BlankEval.INSTANCE;
             case HSSFCell.CELL_TYPE_ERROR:
-                retval = ErrorEval.UNKNOWN_ERROR; // TODO: think about this...
-                break;
-            }
+                return ErrorEval.valueOf(cell.getErrorCellValue());
         }
-        return retval;
+        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()) {
+    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:
-                stack.push(new Ref2DEval(ptg, new NumberEval(cell.getNumericCellValue()), false));
-                break;
+                return new Ref2DEval(ptg, new NumberEval(cell.getNumericCellValue()));
             case HSSFCell.CELL_TYPE_STRING:
-                stack.push(new Ref2DEval(ptg, new StringEval(cell.getRichStringCellValue().getString()), false));
-                break;
+                return new Ref2DEval(ptg, new StringEval(cell.getRichStringCellValue().getString()));
             case HSSFCell.CELL_TYPE_FORMULA:
-                stack.push(new Ref2DEval(ptg, internalEvaluate(cell, row, sheet, workbook), true));
-                break;
+                return new Ref2DEval(ptg, internalEvaluate(cell, row, sheet, workbook));
             case HSSFCell.CELL_TYPE_BOOLEAN:
-                stack.push(new Ref2DEval(ptg, cell.getBooleanCellValue() ? BoolEval.TRUE : BoolEval.FALSE, false));
-                break;
+                return new Ref2DEval(ptg, BoolEval.valueOf(cell.getBooleanCellValue()));
             case HSSFCell.CELL_TYPE_BLANK:
-                stack.push(new Ref2DEval(ptg, BlankEval.INSTANCE, false));
-                break;
+                return new Ref2DEval(ptg, BlankEval.INSTANCE);
             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));
+                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()) {
+        if (cell == null) {
+            return new Ref3DEval(ptg, BlankEval.INSTANCE);
+        }
+        switch (cell.getCellType()) {
             case HSSFCell.CELL_TYPE_NUMERIC:
-                stack.push(new Ref3DEval(ptg, new NumberEval(cell.getNumericCellValue()), false));
-                break;
+                return new Ref3DEval(ptg, new NumberEval(cell.getNumericCellValue()));
             case HSSFCell.CELL_TYPE_STRING:
-                stack.push(new Ref3DEval(ptg, new StringEval(cell.getRichStringCellValue().getString()), false));
-                break;
+                return new Ref3DEval(ptg, new StringEval(cell.getRichStringCellValue().getString()));
             case HSSFCell.CELL_TYPE_FORMULA:
-                stack.push(new Ref3DEval(ptg, internalEvaluate(cell, row, sheet, workbook), true));
-                break;
+                return new Ref3DEval(ptg, internalEvaluate(cell, row, sheet, workbook));
             case HSSFCell.CELL_TYPE_BOOLEAN:
-                stack.push(new Ref3DEval(ptg, cell.getBooleanCellValue() ? BoolEval.TRUE : BoolEval.FALSE, false));
-                break;
+                return new Ref3DEval(ptg, BoolEval.valueOf(cell.getBooleanCellValue()));
             case HSSFCell.CELL_TYPE_BLANK:
-                stack.push(new Ref3DEval(ptg, BlankEval.INSTANCE, false));
-                break;
+                return new Ref3DEval(ptg, BlankEval.INSTANCE);
             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));
+                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 (executable)
index 0000000..2c1632e
Binary files /dev/null and b/src/scratchpad/testcases/org/apache/poi/hdgf/data/ShortChunk1.vsd differ
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 (executable)
index 0000000..7d9a3ce
Binary files /dev/null and b/src/scratchpad/testcases/org/apache/poi/hdgf/data/ShortChunk2.vsd differ
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 (executable)
index 0000000..c8bd7a1
Binary files /dev/null and b/src/scratchpad/testcases/org/apache/poi/hdgf/data/ShortChunk3.vsd differ
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 (file)
index 54a7edb..0000000
Binary files a/src/scratchpad/testcases/org/apache/poi/hssf/data/42464-ExpPtg-bad.xls and /dev/null differ
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 (file)
index 5ae84bc..0000000
Binary files a/src/scratchpad/testcases/org/apache/poi/hssf/data/42464-ExpPtg-ok.xls and /dev/null differ
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/data/44297.xls b/src/scratchpad/testcases/org/apache/poi/hssf/data/44297.xls
deleted file mode 100755 (executable)
index bc65efd..0000000
Binary files a/src/scratchpad/testcases/org/apache/poi/hssf/data/44297.xls and /dev/null differ
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 (file)
index cf4b6fa..0000000
Binary files a/src/scratchpad/testcases/org/apache/poi/hssf/data/FormulaEvalTestData.xls and /dev/null differ
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/data/MissingBits.xls b/src/scratchpad/testcases/org/apache/poi/hssf/data/MissingBits.xls
deleted file mode 100644 (file)
index 4dba467..0000000
Binary files a/src/scratchpad/testcases/org/apache/poi/hssf/data/MissingBits.xls and /dev/null differ
index 9c2cb2b87fdfad108f156b483c4596831821fd62..a038a964eec673a2db3e8e230ca5d7e27d0f6bb8 100644 (file)
 ==================================================================== */
 
 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;
-       
-       public TestMissingRecordAwareHSSFListener() {
-               dirname = System.getProperty("HSSF.testdata.path");
-       }
+/**
+ * Tests for MissingRecordAwareHSSFListener
+ */
+public final class TestMissingRecordAwareHSSFListener extends TestCase {
        
-       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;
+               }
        }
 }
index aa73714a0fe8bb185ab962b19798aa8bb1b5a1b3..0141e1b2a4611836508cd113d908fb51518e8b3b 100644 (file)
@@ -1,4 +1,3 @@
-
 /* ====================================================================
    Licensed to the Apache Software Foundation (ASF) under one or more
    contributor license agreements.  See the NOTICE file distributed with
    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 (executable)
index 0000000..3260c37
--- /dev/null
@@ -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 <tt>org.apache.poi.hssf.record.formula.eval</tt>.
+ * 
+ * @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 (file)
index b53e8eb..0000000
+++ /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 &lt; amolweb at ya hoo dot com &gt;
- *
- */
-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 (executable)
index 0000000..72db658
--- /dev/null
@@ -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 (file)
index ac3ca2e..0000000
+++ /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 &lt; amolweb at ya hoo dot com &gt;
- */
-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 (executable)
index 0000000..27e3338
--- /dev/null
@@ -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 (file)
index 0000000..f57221c
--- /dev/null
@@ -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.<p/>
+ * This class does not test implementors of <tt>Function</tt> and <tt>OperationEval</tt> in
+ * isolation.  Much of the evaluation engine (i.e. <tt>HSSFFormulaEvaluator</tt>, ...) gets
+ * exercised as well.  Tests for bug fixes and specific/tricky behaviour can be found in the
+ * corresponding test class (<tt>TestXxxx</tt>) of the target (<tt>Xxxx</tt>) implementor, 
+ * where execution can be observed more easily.
+ * 
+ * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
+ */
+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 = "<END-OF-FUNCTIONS>";
+       
+               /**
+                * 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 <code>null</code> 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<stes.length) {
+                       if(!stes[startIx].getClassName().equals(Assert.class.getName())) {
+                               break;
+                       }
+                       startIx++;
+               }
+               // skip bottom frames (part of junit framework)
+               int endIx = startIx+1;
+               while(endIx < stes.length) {
+                       if(stes[endIx].getClassName().equals(TestCase.class.getName())) {
+                               break;
+                       }
+                       endIx++;
+               }
+               if(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; i<endIx; i++) {
+                       ps.println("\tat " + stes[i].toString());
+               }
+               
+       }
+
+       /**
+     * @return <code>null</code> 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 (executable)
index 0000000..724c54c
--- /dev/null
@@ -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)<br/>
+        * 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);
+       }
+
+}
index b5e08436714339246c6fe5db5dba0ba93ea41e42..d3e9c4c41220d85e8c25749ef1a353c253260ace 100755 (executable)
@@ -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;
        }
 
index 958c48664958bb69cabdb28b4c437a3060ce0587..a6e262b8682797d9d111613ebbc08dc496ff05a2 100755 (executable)
@@ -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);
        }
 }
index 87405a49180eaa477ebbc5b89d09a4d2281dbe82..d477231349644c4c31f39a6cd311f0a618721ec8 100755 (executable)
@@ -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.
+        * <p/>
+        * 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 (executable)
index 0000000..4f0e5ff
--- /dev/null
@@ -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);
+                       
+       }
+}
index fbaace921023cfed6401d5d7d3727563ee85f6ae..ae93a2d41b694b444fd3fd723dbde0ddca714de2 100755 (executable)
@@ -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 (file)
index 8337810..0000000
+++ /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 &lt; amolweb at ya hoo dot com &gt;
- */
-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 (executable)
index 0000000..7ce2bd2
--- /dev/null
@@ -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 (executable)
index 0000000..a96fb4e
--- /dev/null
@@ -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 (file)
index 0000000..071ca0f
--- /dev/null
@@ -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.<p/>
+ * 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
+ * (<tt>TestXxxx</tt>) of the target (<tt>Xxxx</tt>) 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 = "<end>";
+           /** Used to indicate that the test on the current row should be ignored */
+               public static final String SKIP_CURRENT_TEST_CASE_MARKER = "<skip>";
+       
+       }
+
+       // 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 "<blank>";
+                       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 "<blank>";
+                       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<maxRows; rowIndex++) {
+            HSSFRow r = sheet.getRow(rowIndex);
+                       String newMarkerValue = getMarkerColumnValue(r);
+                       if(r == null) {
+                               continue;
+                       }
+                       if(SS.TEST_CASES_END_MARKER.equalsIgnoreCase(newMarkerValue)) {
+                               // normal exit point
+                               return result;
+                       }
+                       if(SS.SKIP_CURRENT_TEST_CASE_MARKER.equalsIgnoreCase(newMarkerValue)) {
+                               // currently disabled test case row
+                               continue;
+                       }
+                       if(newMarkerValue != null) {
+                               currentGroupComment = newMarkerValue;
+                       }
+            HSSFCell c = r.getCell(SS.COLUMN_INDEX_EVALUATION);
+                       if (c == null || c.getCellType() != HSSFCell.CELL_TYPE_FORMULA) {
+                               continue;
+                       }
+                       evaluator.setCurrentRow(r);
+                       CellValue actualValue = evaluator.evaluate(c);
+                       HSSFCell expectedValueCell = r.getCell(SS.COLUMN_INDEX_EXPECTED_RESULT);
+                       String rowComment = getRowCommentColumnValue(r);
+                       
+                       String msgPrefix = formatTestCaseDetails(sheetName, r.getRowNum(), c, currentGroupComment, rowComment);
+                       try {
+                               confirmExpectedResult(msgPrefix, expectedValueCell, actualValue);
+                               _evaluationSuccessCount ++;
+                               if(result != Result.SOME_EVALUATIONS_FAILED) {
+                                       result = Result.ALL_EVALUATIONS_SUCCEEDED;
+                               }
+                       } catch (RuntimeException e) {
+                               _evaluationFailureCount ++;
+                               printShortStackTrace(System.err, e);
+                               result = Result.SOME_EVALUATIONS_FAILED;
+                       } catch (AssertionFailedError e) {
+                               _evaluationFailureCount ++;
+                               printShortStackTrace(System.err, e);
+                               result = Result.SOME_EVALUATIONS_FAILED;
+                       }
+                       
+               }
+               throw new RuntimeException("Missing end marker '" + SS.TEST_CASES_END_MARKER 
+                               + "' on sheet '" + sheetName + "'");
+               
+       }
+
+
+       private static String formatTestCaseDetails(String sheetName, int rowNum, HSSFCell c, String currentGroupComment,
+                       String rowComment) {
+               
+               StringBuffer sb = new StringBuffer();
+               CellReference cr = new CellReference(sheetName, rowNum, c.getCellNum(), false, false);
+               sb.append(cr.formatAsString());
+               sb.append(" {=").append(c.getCellFormula()).append("}");
+               
+               if(currentGroupComment != null) {
+                       sb.append(" '");
+                       sb.append(currentGroupComment);
+                       if(rowComment != null) {
+                               sb.append(" - ");
+                               sb.append(rowComment);
+                       }
+                       sb.append("' ");
+               } else {
+                       if(rowComment != null) {
+                               sb.append(" '");
+                               sb.append(rowComment);
+                               sb.append("' ");
+                       }
+               }
+               
+               return sb.toString();
+       }
+
+       /**
+     * Asserts that the 'read me' comment page exists, and has this class' name in one of the 
+     * cells.  This back-link is to make it easy to find this class if a reader encounters the 
+     * spreadsheet first.
+     */
+    private void confirmReadMeSheet(HSSFWorkbook workbook) {
+               String firstSheetName = workbook.getSheetName(0);
+               if(!firstSheetName.equalsIgnoreCase(SS.README_SHEET_NAME)) {
+                       throw new RuntimeException("First sheet's name was '" + firstSheetName + "' but expected '" + SS.README_SHEET_NAME + "'");
+               }
+               HSSFSheet sheet = workbook.getSheetAt(0);
+               String specifiedClassName = sheet.getRow(2).getCell((short)0).getRichStringCellValue().getString();
+               assertEquals("Test class name in spreadsheet comment", getClass().getName(), specifiedClassName);
+               
+       }
+
+
+    /**
+     * Useful to keep output concise when expecting many failures to be reported by this test case
+     */
+       private static void printShortStackTrace(PrintStream ps, Throwable e) {
+               StackTraceElement[] stes = e.getStackTrace();
+               
+               int startIx = 0;
+               // skip any top frames inside junit.framework.Assert
+               while(startIx<stes.length) {
+                       if(!stes[startIx].getClassName().equals(Assert.class.getName())) {
+                               break;
+                       }
+                       startIx++;
+               }
+               // skip bottom frames (part of junit framework)
+               int endIx = startIx+1;
+               while(endIx < stes.length) {
+                       if(stes[endIx].getClassName().equals(TestCase.class.getName())) {
+                               break;
+                       }
+                       endIx++;
+               }
+               if(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; i<endIx; i++) {
+                       ps.println("\tat " + stes[i].toString());
+               }
+               
+       }
+
+       private static String getRowCommentColumnValue(HSSFRow r) {
+               return getCellTextValue(r, SS.COLUMN_ROW_COMMENT, "row comment");
+       }
+       
+       private static String getMarkerColumnValue(HSSFRow r) {
+               return getCellTextValue(r, SS.COLUMN_INDEX_MARKER, "marker");
+       }
+       
+       /**
+        * @return <code>null</code> 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 (executable)
index 0000000..d275e5f
--- /dev/null
@@ -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 <code>new Area2DEval(new AreaPtg(ref), values)</code>
+        */
+       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 <tt>AreaEval</tt>.<br/>
+        * 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 (executable)
index 0000000..dc3d595
--- /dev/null
@@ -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 (executable)
index 0000000..f114662
--- /dev/null
@@ -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 (executable)
index 0000000..a6ce345
--- /dev/null
@@ -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 (executable)
index 0000000..7304391
--- /dev/null
@@ -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 (executable)
index 0000000..4d63cad
--- /dev/null
@@ -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 (executable)
index 0000000..076ac1f
--- /dev/null
@@ -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 (executable)
index 0000000..c9f043b
--- /dev/null
@@ -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);
+               
+       }
+}
index 3b31cc03a3d2460965cfc22661b7501fa19043ac..c849fd4369211fac513871d29646a6ddda4b141b 100644 (file)
@@ -21,14 +21,14 @@ import java.io.FileInputStream;
 import java.util.Iterator;
 import java.util.List;
 
+import junit.framework.TestCase;
+
 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.usermodel.HSSFFormulaEvaluator.CellValue;
 import org.apache.poi.hssf.util.CellReference;
 
-import junit.framework.TestCase;
-
-public class TestBug42464 extends TestCase {
+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<ptgs.size(); i++) {
-                                               String c = ptgs.get(i).getClass().toString();
-                                               System.out.println("\t" + c.substring(c.lastIndexOf('.')+1) );
-                                       }
-                                       System.out.println("-> " + cell.getCellFormula());
+                       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<ptgs.size(); i++) {
+                                       String c = ptgs.get(i).getClass().toString();
+                                       System.out.println("\t" + c.substring(c.lastIndexOf('.')+1) );
                                }
-                               
-                               eval.evaluate(cell);
-                               
+                               System.out.println("-> " + 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 (file)
index 0000000..27c3bdc
--- /dev/null
@@ -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 (file)
index 0000000..3362f3c
--- /dev/null
@@ -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 (executable)
index 0000000..191b719
--- /dev/null
@@ -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)<br/>
+ * 
+ * @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;
+    }
+}
index be3d9b0a782a9bf450d1ea3b98fc4cc55491540a..5d4d7df19e46db191386ef2f31da44f5fc8d3ab3 100644 (file)
@@ -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 (executable)
index 0000000..9b5fc0b
--- /dev/null
@@ -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<br/>
+ * 
+ * @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 (executable)
index 0000000..5954c9c
--- /dev/null
@@ -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;
+    }
+}
index 1e0edd6825a7300c820f3259f91e3b9885420bef..5b597a67c873f37f49346dc113dfbc99c97f9830 100644 (file)
@@ -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<br/>
+ * 
+ * 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/testcases/org/apache/poi/hssf/data/44297.xls b/src/testcases/org/apache/poi/hssf/data/44297.xls
new file mode 100755 (executable)
index 0000000..bc65efd
Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/44297.xls differ
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 (executable)
index 0000000..788865b
Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/AbnormalSharedFormulaFlag.xls differ
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 (file)
index 0000000..6260d87
Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/FormulaEvalTestData.xls differ
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 (executable)
index 0000000..f4b35fb
Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/LookupFunctionsTestCaseData.xls differ
diff --git a/src/testcases/org/apache/poi/hssf/data/MissingBits.xls b/src/testcases/org/apache/poi/hssf/data/MissingBits.xls
new file mode 100644 (file)
index 0000000..4dba467
Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/MissingBits.xls differ
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 (file)
index 0000000..dcaa79b
Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/OddStyleRecord.xls differ
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 (file)
index 0000000..d479b94
Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/ReadOnlyRecommended.xls differ
diff --git a/src/testcases/org/apache/poi/hssf/data/SingleLetterRanges.xls b/src/testcases/org/apache/poi/hssf/data/SingleLetterRanges.xls
new file mode 100644 (file)
index 0000000..e35058d
Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/SingleLetterRanges.xls differ
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 (file)
index 0b2a869..0000000
Binary files a/src/testcases/org/apache/poi/hssf/data/TestDataValidation.xls and /dev/null differ
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 (executable)
index 0000000..07eafb4
Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/externalFunctionExample.xls differ
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 (executable)
index 0000000..90a915a
Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/logoKarmokar4.png differ
index 1b41d87d4d97baccfe8c6d06a21af6dc1fc5ebbe..f922e75d8280aeacdee06f9479a404f9530a6e62 100644 (file)
@@ -1,4 +1,3 @@
-
 /* ====================================================================
    Licensed to the Apache Software Foundation (ASF) under one or more
    contributor license agreements.  See the NOTICE file distributed with
         
 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 <code>null</code>
+     */
+    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)<br/>
+     * 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 (executable)
index 0000000..b1acfea
--- /dev/null
@@ -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 <tt>org.apache.poi.hssf.record</tt>.
+ * 
+ * @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 (executable)
index 0000000..bcfb766
--- /dev/null
@@ -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());
+    }
+}
index fca5bae5451c3ad93d504ebda19b2047e17e0489..3727989efb1670ca3004d78d72d16dedf1a2b513 100644 (file)
@@ -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);
 
-        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, 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);
+
+        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);
     }
-    
-
 }
index 767f507e73ebaf5b12a248913960f5743b49c666..ecb55ca82c1da95192744f0f55681489f09fb3b6 100755 (executable)
@@ -1,4 +1,3 @@
-
 /* ====================================================================
    Licensed to the Apache Software Foundation (ASF) under one or more
    contributor license agreements.  See the NOTICE file distributed with
    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]);
+        
+    }
 }
index a3315c29780a3a1d307b9fab873f3465aeb27663..8e8a72ece7288b03af3df150f75767ef8b19e3f4 100755 (executable)
 
 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 (<tt>FormulaRecord.isSharedFormula()</tt>) 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).<br/>
+     * So it is important that the code which locates the SharedFormulaRecord for each 
+     * FormulaRecord does not allow matches across sheets.</br> 
+     * 
+     * Prior to bugzilla 44449 (Feb 2008), POI <tt>ValueRecordsAggregate.construct(int, List)</tt> 
+     * allowed <tt>SharedFormulaRecord</tt>s to be erroneously used across sheets.  That incorrect
+     * behaviour is shown by this test.<p/>
+     * 
+     * <b>Notes on how to produce the test spreadsheet</b>:</p>
+     * The setup for this test (AbnormalSharedFormulaFlag.xls) is rather fragile, insomuchas 
+     * re-saving the file (either with Excel or POI) clears the flag.<br/>
+     * <ol>
+     * <li>A new spreadsheet was created in Excel (File | New | Blank Workbook).</li>
+     * <li>Sheet3 was deleted.</li>
+     * <li>Sheet2!A1 formula was set to '="second formula"', and fill-dragged through A1:A8.</li>
+     * <li>Sheet1!A1 formula was set to '="first formula"', and also fill-dragged through A1:A8.</li>
+     * <li>Four rows on Sheet1 "5" through "8" were deleted ('delete rows' alt-E D, not 'clear' Del).</li>
+     * <li>The spreadsheet was saved as AbnormalSharedFormulaFlag.xls.</li>
+     * </ol>
+     * Prior to the row delete action the spreadsheet has two <tt>SharedFormulaRecord</tt>s. One 
+     * for each sheet. To expose the bug, the shared formulas have been made to overlap.<br/>
+     * The row delete action (as described here) seems to to delete the 
+     * <tt>SharedFormulaRecord</tt> from Sheet1 (but not clear the 'shared formula' flags.<br/>
+     * 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();
+    }
+
 }
index 21e6a8ba3c3d2a12ad8610ac5b18ec4b25f36b6a..0912b97611b7c4e891e4e2385e4ccd6ab571ba84 100644 (file)
@@ -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;
        }
-    
 }
index b12681338777d3c1aa47f3d401495a38e546b8d9..92ca4ba04419583cbbe4c54bc2f6987f57c0306b 100644 (file)
@@ -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);
index 522a5bcf2853f10d1f5b4c44c07e36c0e3ffea93..3a7f2f29a418c14abbb94c089bb7971e2ab9db74 100644 (file)
@@ -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 (executable)
index 0000000..8c89dad
--- /dev/null
@@ -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 <tt>NameXPtg.toFormulaString(Workbook)</tt> 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 (executable)
index 0000000..cbc555d
--- /dev/null
@@ -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 <tt>org.apache.poi.hssf.usermodel</tt> 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;
+       }
+}
index 6dfdddad44f8042a3425ea6f771314f9164177dc..f9bb362c7b0b87743c5ac3da4a00009f3f739b17 100644 (file)
@@ -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());
+    }
 }
 
 
index f970ff26f1f5db48b36567e0a98d01be8c90922b..34885e7a21d88ec843ba926776cdf4006099fe74 100644 (file)
@@ -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 );
index f1e8f497773350e24e788fc74a469072b8f00629..9eb12bd4e1794909dae5eb4f344f232e5c818061 100644 (file)
@@ -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(),(
index c5674b9e765a57c0f01284ff7a8741bac1866cf8..a0f09696b544fa51fd42e428cb4214f8ed47f8a9 100644 (file)
@@ -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
index 25d6684c4714c8f36c96e56a2df35185348f1420..fd01001f84928a5eaf150750d8c1bb14820107fd 100644 (file)
 */\r
 package org.apache.poi.hssf.usermodel;\r
 \r
-import junit.framework.TestCase;\r
-\r
-import java.io.IOException;\r
-import java.io.FileInputStream;\r
 import java.io.ByteArrayOutputStream;\r
+import java.io.File;\r
+import java.io.FileInputStream;\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+\r
+import junit.framework.TestCase;\r
 \r
 /**\r
  * Test <code>HSSFPicture</code>.\r
  *\r
  * @author Yegor Kozlov (yegor at apache.org)\r
  */\r
-public class TestHSSFPicture extends TestCase{\r
+public final class TestHSSFPicture extends TestCase{\r
 \r
-    public void testResize() throws Exception {\r
+    public void testResize() {\r
         HSSFWorkbook wb = new HSSFWorkbook();\r
         HSSFSheet sh1 = wb.createSheet();\r
         HSSFPatriarch p1 = sh1.createDrawingPatriarch();\r
 \r
-        int idx1 = loadPicture( "src/resources/logos/logoKarmokar4.png", wb);\r
+        byte[] pictureData = getTestDataFileContent("logoKarmokar4.png");\r
+        int idx1 = wb.addPicture( pictureData, HSSFWorkbook.PICTURE_TYPE_PNG );\r
         HSSFPicture picture1 = p1.createPicture(new HSSFClientAnchor(), idx1);\r
         HSSFClientAnchor anchor1 = picture1.getPreferredSize();\r
 \r
@@ -52,28 +55,25 @@ public class TestHSSFPicture extends TestCase{
     /**\r
      * Copied from org.apache.poi.hssf.usermodel.examples.OfficeDrawing\r
      */\r
-    private static int loadPicture( String path, HSSFWorkbook wb ) throws IOException\r
-    {\r
-        int pictureIndex;\r
-        FileInputStream fis = null;\r
-        ByteArrayOutputStream bos = null;\r
-        try\r
-        {\r
-            fis = new FileInputStream( path);\r
-            bos = new ByteArrayOutputStream( );\r
-            int c;\r
-            while ( (c = fis.read()) != -1)\r
-                bos.write( c );\r
-            pictureIndex = wb.addPicture( bos.toByteArray(), HSSFWorkbook.PICTURE_TYPE_PNG );\r
-        }\r
-        finally\r
-        {\r
-            if (fis != null)\r
-                fis.close();\r
-            if (bos != null)\r
-                bos.close();\r
-        }\r
-        return pictureIndex;\r
-    }\r
+     private static byte[] getTestDataFileContent(String fileName) {\r
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();\r
+\r
+        String readFilename = System.getProperty("HSSF.testdata.path");\r
+        try {\r
+            InputStream fis = new FileInputStream(readFilename+File.separator+fileName);\r
 \r
+            byte[] buf = new byte[512];\r
+            while(true) {\r
+                int bytesRead = fis.read(buf);\r
+                if(bytesRead < 1) {\r
+                    break;\r
+                }\r
+                bos.write(buf, 0, bytesRead);\r
+            }\r
+            fis.close();\r
+        } catch (IOException e) {\r
+            throw new RuntimeException(e);\r
+        }\r
+        return bos.toByteArray();\r
+     }\r
 }\r
index 44c03cd20de857e908b4204d41cca9183e00a583..b6f22022c67b7c5b80b5ec3da9d62b6e009bd99e 100644 (file)
@@ -1,4 +1,3 @@
-
 /* ====================================================================
    Licensed to the Apache Software Foundation (ASF) under one or more
    contributor license agreements.  See the NOTICE file distributed with
    See the License for the specific language governing permissions and
    limitations under the License.
 ==================================================================== */
-        
 
 package org.apache.poi.hssf.usermodel;
 
-import junit.framework.TestCase;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 
-import org.apache.poi.util.TempFile;
+import junit.framework.TestCase;
 
 /**
  * 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());
     }
-    
 }
index f22de1758cef21b35e731782a87a29eb2a37a253..afbbde026e96eda70d80aa204cf55f221d969e38 100644 (file)
@@ -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<iSize; i++) {
-            CellReference cref = crefs[i];
-            assertNotNull(cref);
-            HSSFSheet s = wb.getSheet(cref.getSheetName());
-            HSSFRow r = sheet.getRow(cref.getRow());
-            HSSFCell c = r.getCell(cref.getCol());
-            String contents = c.getStringCellValue();
-            assertEquals("Contents of cell retrieved by its named reference", contents, cellValue);
-        }
+        assertTrue("Should be exactly 1 cell in the named cell :'" +cellName+"'", aref.isSingleCell());
+        
+        CellReference cref = aref.getFirstCell();
+        assertNotNull(cref);
+        HSSFSheet s = wb.getSheet(cref.getSheetName());
+        assertNotNull(s);
+        HSSFRow r = sheet.getRow(cref.getRow());
+        HSSFCell c = r.getCell(cref.getCol());
+        String contents = c.getRichStringCellValue().getString();
+        assertEquals("Contents of cell retrieved by its named reference", contents, cellValue);
     }
 
     /**
diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestPOIFSProperties.java b/src/testcases/org/apache/poi/hssf/usermodel/TestPOIFSProperties.java
new file mode 100644 (file)
index 0000000..9fe33ce
--- /dev/null
@@ -0,0 +1,98 @@
+/* ====================================================================
+   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 org.apache.poi.poifs.filesystem.POIFSFileSystem;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.hpsf.SummaryInformation;
+import org.apache.poi.hpsf.PropertySetFactory;
+
+import java.io.FileInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+
+import junit.framework.TestCase;
+
+/**
+ * Old-style setting of POIFS properties doesn't work with POI 3.0.2
+ *
+ * @author Yegor Kozlov
+ */
+public class TestPOIFSProperties extends TestCase{
+    protected String cwd = System.getProperty("HSSF.testdata.path");
+
+    protected String title = "Testing POIFS properties";
+
+    public void testFail() throws Exception {
+        FileInputStream is = new FileInputStream(new File(cwd, "Simple.xls"));
+        POIFSFileSystem fs = new POIFSFileSystem(is);
+        is.close();
+
+        HSSFWorkbook wb = new HSSFWorkbook(fs);
+
+        //set POIFS properties after constructing HSSFWorkbook
+        //(a piece of code that used to work up to POI 3.0.2)
+        SummaryInformation summary1 = (SummaryInformation)PropertySetFactory.create(fs.createDocumentInputStream(SummaryInformation.DEFAULT_STREAM_NAME));
+        summary1.setTitle(title);
+        //write the modified property back to POIFS
+        fs.getRoot().getEntry(SummaryInformation.DEFAULT_STREAM_NAME).delete();
+        fs.createDocument(summary1.toInputStream(), SummaryInformation.DEFAULT_STREAM_NAME);
+
+        //save the workbook and read the property
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        wb.write(out);
+        out.close();
+
+        POIFSFileSystem fs2 = new POIFSFileSystem(new ByteArrayInputStream(out.toByteArray()));
+        SummaryInformation summary2 = (SummaryInformation)PropertySetFactory.create(fs2.createDocumentInputStream(SummaryInformation.DEFAULT_STREAM_NAME));
+
+        try {
+            //failing assertion
+            assertEquals(title, summary2.getTitle());
+
+        } catch (AssertionError e){
+            assertTrue(true);
+        }
+    }
+
+    public void testOK() throws Exception {
+        FileInputStream is = new FileInputStream(new File(cwd, "Simple.xls"));
+        POIFSFileSystem fs = new POIFSFileSystem(is);
+        is.close();
+
+        //set POIFS properties before constructing HSSFWorkbook
+        SummaryInformation summary1 = (SummaryInformation)PropertySetFactory.create(fs.createDocumentInputStream(SummaryInformation.DEFAULT_STREAM_NAME));
+        summary1.setTitle(title);
+
+        fs.getRoot().getEntry(SummaryInformation.DEFAULT_STREAM_NAME).delete();
+        fs.createDocument(summary1.toInputStream(), SummaryInformation.DEFAULT_STREAM_NAME);
+
+        HSSFWorkbook wb = new HSSFWorkbook(fs);
+
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        wb.write(out);
+        out.close();
+
+        //read the property
+        POIFSFileSystem fs2 = new POIFSFileSystem(new ByteArrayInputStream(out.toByteArray()));
+        SummaryInformation summary2 = (SummaryInformation)PropertySetFactory.create(fs2.createDocumentInputStream(SummaryInformation.DEFAULT_STREAM_NAME));
+        assertEquals(title, summary2.getTitle());
+
+    }
+}
diff --git a/src/testcases/org/apache/poi/hssf/util/AllHSSFUtilTests.java b/src/testcases/org/apache/poi/hssf/util/AllHSSFUtilTests.java
new file mode 100755 (executable)
index 0000000..1f7aa5f
--- /dev/null
@@ -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.hssf.util;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Collects all tests for org.apache.poi.hssf.util.
+ * 
+ * @author Josh Micich
+ */
+public class AllHSSFUtilTests {
+       
+       public static Test suite() {
+               TestSuite result = new TestSuite("Tests for org.apache.poi.hssf.util");
+        result.addTestSuite(TestAreaReference.class);
+        result.addTestSuite(TestCellReference.class);
+        result.addTestSuite(TestRangeAddress.class);
+        result.addTestSuite(TestRKUtil.class);
+        result.addTestSuite(TestSheetReferences.class);
+               return result;
+       }
+}
index ccd2a580523df2019724e2f5953924fca76ec944..afdda4435785f28ae88d783fcff8e160496b7fbe 100644 (file)
@@ -1,4 +1,3 @@
-
 /* ====================================================================
    Licensed to the Apache Software Foundation (ASF) under one or more
    contributor license agreements.  See the NOTICE file distributed with
@@ -29,29 +28,28 @@ import org.apache.poi.hssf.record.formula.Area3DPtg;
 import org.apache.poi.hssf.record.formula.UnionPtg;
 
 import java.io.FileInputStream;
+import java.io.IOException;
 import java.io.InputStream;
 import java.util.List;
 
-public class TestAreaReference extends TestCase {
-     public TestAreaReference(String s) {
-        super(s);
-    }
+public final class TestAreaReference extends TestCase {
+
     public void testAreaRef1() {
         AreaReference ar = new AreaReference("$A$1:$B$2");
-        assertTrue("Two cells expected",ar.getCells().length == 2);
-        CellReference cf = ar.getCells()[0];
+        assertFalse("Two cells expected", ar.isSingleCell());
+        CellReference cf = ar.getFirstCell();
         assertTrue("row is 4",cf.getRow()==0);
         assertTrue("col is 1",cf.getCol()==0);
         assertTrue("row is abs",cf.isRowAbsolute());
         assertTrue("col is abs",cf.isColAbsolute());
-        assertTrue("string is $A$1",cf.toString().equals("$A$1"));
+        assertTrue("string is $A$1",cf.formatAsString().equals("$A$1"));
         
-        cf = ar.getCells()[1];
+        cf = ar.getLastCell();
         assertTrue("row is 4",cf.getRow()==1);
         assertTrue("col is 1",cf.getCol()==1);
         assertTrue("row is abs",cf.isRowAbsolute());
         assertTrue("col is abs",cf.isColAbsolute());
-        assertTrue("string is $B$2",cf.toString().equals("$B$2"));
+        assertTrue("string is $B$2",cf.formatAsString().equals("$B$2"));
         
         CellReference[] refs = ar.getAllReferencedCells();
         assertEquals(4, refs.length);
@@ -78,62 +76,31 @@ public class TestAreaReference extends TestCase {
      * Reported by Arne.Clauss@gedas.de
      */
     public void testReferenceWithSheet() {
-       String ref = "Tabelle1!B5";
-               AreaReference myAreaReference = new AreaReference(ref);
-               CellReference[] myCellReference = myAreaReference.getCells();
-
-               assertEquals(1, myCellReference.length);
-               assertNotNull("cell reference not null : "+myCellReference[0]);
-       assertEquals("Not Column B", (short)1,myCellReference[0].getCol());
-               assertEquals("Not Row 5", 4,myCellReference[0].getRow());
-               assertEquals("Shouldn't be absolute", false, myCellReference[0].isRowAbsolute());
-               assertEquals("Shouldn't be absolute", false, myCellReference[0].isColAbsolute());
-               
-               assertEquals(1, myAreaReference.getAllReferencedCells().length);
-               
-               
-               ref = "Tabelle1!$B$5:$B$7";
-               myAreaReference = new AreaReference(ref);
-               myCellReference = myAreaReference.getCells();
-               assertEquals(2, myCellReference.length);
-               
-               assertEquals("Tabelle1", myCellReference[0].getSheetName());
-               assertEquals(4, myCellReference[0].getRow());
-               assertEquals(1, myCellReference[0].getCol());
-               assertTrue(myCellReference[0].isRowAbsolute());
-               assertTrue(myCellReference[0].isColAbsolute());
-               
-               assertEquals("Tabelle1", myCellReference[1].getSheetName());
-               assertEquals(6, myCellReference[1].getRow());
-               assertEquals(1, myCellReference[1].getCol());
-               assertTrue(myCellReference[1].isRowAbsolute());
-               assertTrue(myCellReference[1].isColAbsolute());
-               
-               // And all that make it up
-               myCellReference = myAreaReference.getAllReferencedCells();
-               assertEquals(3, myCellReference.length);
-               
-               assertEquals("Tabelle1", myCellReference[0].getSheetName());
-               assertEquals(4, myCellReference[0].getRow());
-               assertEquals(1, myCellReference[0].getCol());
-               assertTrue(myCellReference[0].isRowAbsolute());
-               assertTrue(myCellReference[0].isColAbsolute());
-               
-               assertEquals("Tabelle1", myCellReference[1].getSheetName());
-               assertEquals(5, myCellReference[1].getRow());
-               assertEquals(1, myCellReference[1].getCol());
-               assertTrue(myCellReference[1].isRowAbsolute());
-               assertTrue(myCellReference[1].isColAbsolute());
-               
-               assertEquals("Tabelle1", myCellReference[2].getSheetName());
-               assertEquals(6, myCellReference[2].getRow());
-               assertEquals(1, myCellReference[2].getCol());
-               assertTrue(myCellReference[2].isRowAbsolute());
-               assertTrue(myCellReference[2].isColAbsolute());
+        AreaReference ar;
+        
+        ar = new AreaReference("Tabelle1!B5");
+        assertTrue(ar.isSingleCell());
+        TestCellReference.confirmCell(ar.getFirstCell(), "Tabelle1", 4, 1, false, false, "Tabelle1!B5");
+        
+        assertEquals(1, ar.getAllReferencedCells().length);
+        
+        
+        ar = new AreaReference("Tabelle1!$B$5:$B$7");
+        assertFalse(ar.isSingleCell());
+        
+        TestCellReference.confirmCell(ar.getFirstCell(), "Tabelle1", 4, 1, true, true, "Tabelle1!$B$5");
+        TestCellReference.confirmCell(ar.getLastCell(), "Tabelle1", 6, 1, true, true, "Tabelle1!$B$7");
+        
+        // And all that make it up
+        CellReference[] allCells = ar.getAllReferencedCells();
+        assertEquals(3, allCells.length);
+        TestCellReference.confirmCell(allCells[0], "Tabelle1", 4, 1, true, true, "Tabelle1!$B$5");
+        TestCellReference.confirmCell(allCells[1], "Tabelle1", 5, 1, true, true, "Tabelle1!$B$6");
+        TestCellReference.confirmCell(allCells[2], "Tabelle1", 6, 1, true, true, "Tabelle1!$B$7");
     }
 
     private static class HSSFWB extends HSSFWorkbook {
-        private HSSFWB(InputStream in) throws Exception {
+        public HSSFWB(InputStream in) throws IOException {
             super(in);
         }
         public Workbook getWorkbook() {
@@ -176,42 +143,42 @@ public class TestAreaReference extends TestCase {
 
         refs = AreaReference.generateContiguous(refSimple);
         assertEquals(1, refs.length);
-        assertEquals(1, refs[0].getDim());
-        assertEquals("$C$10", refs[0].toString());
+        assertTrue(refs[0].isSingleCell());
+        assertEquals("$C$10", refs[0].formatAsString());
 
         refs = AreaReference.generateContiguous(ref2D);
         assertEquals(1, refs.length);
-        assertEquals(2, refs[0].getDim());
-        assertEquals("$C$10:$D$11", refs[0].toString());
+        assertFalse(refs[0].isSingleCell());
+        assertEquals("$C$10:$D$11", refs[0].formatAsString());
 
         refs = AreaReference.generateContiguous(refDCSimple);
         assertEquals(3, refs.length);
-        assertEquals(1, refs[0].getDim());
-        assertEquals(1, refs[1].getDim());
-        assertEquals(1, refs[2].getDim());
-        assertEquals("$C$10", refs[0].toString());
-        assertEquals("$D$12", refs[1].toString());
-        assertEquals("$E$14", refs[2].toString());
+        assertTrue(refs[0].isSingleCell());
+        assertTrue(refs[1].isSingleCell());
+        assertTrue(refs[2].isSingleCell());
+        assertEquals("$C$10", refs[0].formatAsString());
+        assertEquals("$D$12", refs[1].formatAsString());
+        assertEquals("$E$14", refs[2].formatAsString());
 
         refs = AreaReference.generateContiguous(refDC2D);
         assertEquals(3, refs.length);
-        assertEquals(2, refs[0].getDim());
-        assertEquals(1, refs[1].getDim());
-        assertEquals(2, refs[2].getDim());
-        assertEquals("$C$10:$C$11", refs[0].toString());
-        assertEquals("$D$12", refs[1].toString());
-        assertEquals("$E$14:$E$20", refs[2].toString());
+        assertFalse(refs[0].isSingleCell());
+        assertTrue(refs[1].isSingleCell());
+        assertFalse(refs[2].isSingleCell());
+        assertEquals("$C$10:$C$11", refs[0].formatAsString());
+        assertEquals("$D$12", refs[1].formatAsString());
+        assertEquals("$E$14:$E$20", refs[2].formatAsString());
 
         refs = AreaReference.generateContiguous(refDC3D);
         assertEquals(2, refs.length);
-        assertEquals(2, refs[0].getDim());
-        assertEquals(2, refs[1].getDim());
-        assertEquals("$C$10:$C$14", refs[0].toString());
-        assertEquals("$D$10:$D$12", refs[1].toString());
-        assertEquals("Tabelle1", refs[0].getCells()[0].getSheetName());
-        assertEquals("Tabelle1", refs[0].getCells()[1].getSheetName());
-        assertEquals("Tabelle1", refs[1].getCells()[0].getSheetName());
-        assertEquals("Tabelle1", refs[1].getCells()[1].getSheetName());
+        assertFalse(refs[0].isSingleCell());
+        assertFalse(refs[0].isSingleCell());
+        assertEquals("Tabelle1!$C$10:$C$14", refs[0].formatAsString());
+        assertEquals("Tabelle1!$D$10:$D$12", refs[1].formatAsString());
+        assertEquals("Tabelle1", refs[0].getFirstCell().getSheetName());
+        assertEquals("Tabelle1", refs[0].getLastCell().getSheetName());
+        assertEquals("Tabelle1", refs[1].getFirstCell().getSheetName());
+        assertEquals("Tabelle1", refs[1].getLastCell().getSheetName());
     }
 
     public void testDiscontinousReference() throws Exception {
@@ -261,22 +228,46 @@ public class TestAreaReference extends TestCase {
         assertFalse(AreaReference.isContiguous(aNamedCell.getReference()));
         AreaReference[] arefs = AreaReference.generateContiguous(aNamedCell.getReference());
         assertEquals(2, arefs.length);
-        assertEquals(rawRefA, arefs[0].toString());
-        assertEquals(rawRefB, arefs[1].toString());
+        assertEquals(refA, arefs[0].formatAsString());
+        assertEquals(refB, arefs[1].formatAsString());
 
         for(int i=0; i<arefs.length; i++) {
-            CellReference[] crefs = arefs[i].getCells();
-            for (int j=0; j<crefs.length; j++) {
-                // Check it turns into real stuff
-                HSSFSheet s = wb.getSheet(crefs[j].getSheetName());
-                HSSFRow r = s.getRow(crefs[j].getRow());
-                HSSFCell c = r.getCell(crefs[j].getCol());
-            }
+            AreaReference ar = arefs[i];
+            confirmResolveCellRef(wb, ar.getFirstCell());
+            confirmResolveCellRef(wb, ar.getLastCell());
         }
     }
+
+    private static void confirmResolveCellRef(HSSFWorkbook wb, CellReference cref) {
+        HSSFSheet s = wb.getSheet(cref.getSheetName());
+        HSSFRow r = s.getRow(cref.getRow());
+        HSSFCell c = r.getCell(cref.getCol());
+        assertNotNull(c);
+    }
+    
+    public void testSpecialSheetNames() {
+        AreaReference ar;
+        ar = new AreaReference("'Sheet A'!A1");
+        confirmAreaSheetName(ar, "Sheet A", "'Sheet A'!A1");
+        
+        ar = new AreaReference("'Hey! Look Here!'!A1");
+        confirmAreaSheetName(ar, "Hey! Look Here!", "'Hey! Look Here!'!A1");
+        
+        ar = new AreaReference("'O''Toole'!A1:B2");
+        confirmAreaSheetName(ar, "O'Toole", "'O''Toole'!A1:B2");
+        
+        ar = new AreaReference("'one:many'!A1:B2");
+        confirmAreaSheetName(ar, "one:many", "'one:many'!A1:B2");
+    }
+
+    private static void confirmAreaSheetName(AreaReference ar, String sheetName, String expectedFullText) {
+        CellReference[] cells = ar.getAllReferencedCells();
+        assertEquals(sheetName, cells[0].getSheetName());
+        assertEquals(expectedFullText, ar.formatAsString());
+    }
     
-    public static void main(java.lang.String[] args) {
-               junit.textui.TestRunner.run(TestAreaReference.class);
-       }
+    public static void main(String[] args) {
+        junit.textui.TestRunner.run(TestAreaReference.class);
+    }
         
 }
index 88b26fe7ad19c1ce403e54efbfb0450a4340ba66..648fb9a8e32e44ec5c58b36356c54a26ebf041f1 100644 (file)
@@ -1,4 +1,3 @@
-
 /* ====================================================================
    Licensed to the Apache Software Foundation (ASF) under one or more
    contributor license agreements.  See the NOTICE file distributed with
@@ -22,87 +21,74 @@ package org.apache.poi.hssf.util;
 import junit.framework.TestCase;
 
 
-public class TestCellReference extends TestCase {
-    public TestCellReference(String s) {
-        super(s);
-    }
+public final class TestCellReference extends TestCase {
     
     public void testAbsRef1(){
         CellReference cf = new CellReference("$B$5");
-        assertTrue("row is 4",cf.getRow()==4);
-        assertTrue("col is 1",cf.getCol()==1);
-        assertTrue("row is abs",cf.isRowAbsolute());
-        assertTrue("col is abs",cf.isColAbsolute());
-        assertTrue("string is $B$5",cf.toString().equals("$B$5"));
+        confirmCell(cf, null, 4, 1, true, true, "$B$5");
     }
     
     public void  testAbsRef2(){
         CellReference cf = new CellReference(4,1,true,true);
-        assertTrue("row is 4",cf.getRow()==4);
-        assertTrue("col is 1",cf.getCol()==1);
-        assertTrue("row is abs",cf.isRowAbsolute());
-        assertTrue("col is abs",cf.isColAbsolute());
-        assertTrue("string is $B$5",cf.toString().equals("$B$5"));
+        confirmCell(cf, null, 4, 1, true, true, "$B$5");
     }
 
     public void  testAbsRef3(){
         CellReference cf = new CellReference("B$5");
-        assertTrue("row is 4",cf.getRow()==4);
-        assertTrue("col is 1",cf.getCol()==1);
-        assertTrue("row is abs",cf.isRowAbsolute());
-        assertTrue("col is rel",!cf.isColAbsolute());
-        assertTrue("string is B$5",cf.toString().equals("B$5"));
+        confirmCell(cf, null, 4, 1, true, false, "B$5");
     }
     
     public void  testAbsRef4(){
         CellReference cf = new CellReference(4,1,true,false);
-        assertTrue("row is 4",cf.getRow()==4);
-        assertTrue("col is 1",cf.getCol()==1);
-        assertTrue("row is abs",cf.isRowAbsolute());
-        assertTrue("col is rel",!cf.isColAbsolute());
-        assertTrue("string is B$5",cf.toString().equals("B$5"));
+        confirmCell(cf, null, 4, 1, true, false, "B$5");
     }
     
     public void  testAbsRef5(){
         CellReference cf = new CellReference("$B5");
-        assertTrue("row is 4",cf.getRow()==4);
-        assertTrue("col is 1",cf.getCol()==1);
-        assertTrue("row is abs",!cf.isRowAbsolute());
-        assertTrue("col is rel",cf.isColAbsolute());
-        assertTrue("string is B$5",cf.toString().equals("$B5"));
+        confirmCell(cf, null, 4, 1, false, true, "$B5");
     }
     
     public void  testAbsRef6(){
         CellReference cf = new CellReference(4,1,false,true);
-        assertTrue("row is 4",cf.getRow()==4);
-        assertTrue("col is 1",cf.getCol()==1);
-        assertTrue("row is abs",!cf.isRowAbsolute());
-        assertTrue("col is rel",cf.isColAbsolute());
-        assertTrue("string is B$5",cf.toString().equals("$B5"));
+        confirmCell(cf, null, 4, 1, false, true, "$B5");
     }
 
     public void  testAbsRef7(){
         CellReference cf = new CellReference("B5");
-        assertTrue("row is 4",cf.getRow()==4);
-        assertTrue("col is 1",cf.getCol()==1);
-        assertTrue("row is abs",!cf.isRowAbsolute());
-        assertTrue("col is rel",!cf.isColAbsolute());
-        assertTrue("string is B$5",cf.toString().equals("B5"));
+        confirmCell(cf, null, 4, 1, false, false, "B5");
     }
     
     public void  testAbsRef8(){
         CellReference cf = new CellReference(4,1,false,false);
-        assertTrue("row is 4",cf.getRow()==4);
-        assertTrue("col is 1",cf.getCol()==1);
-        assertTrue("row is abs",!cf.isRowAbsolute());
-        assertTrue("col is rel",!cf.isColAbsolute());
-        assertTrue("string is B$5",cf.toString().equals("B5"));
+        confirmCell(cf, null, 4, 1, false, false, "B5");
+    }
+    
+    public void testSpecialSheetNames() {
+        CellReference cf;
+        cf = new CellReference("'profit + loss'!A1");
+        confirmCell(cf, "profit + loss", 0, 0, false, false, "'profit + loss'!A1");
+        
+        cf = new CellReference("'O''Brien''s Sales'!A1");
+        confirmCell(cf, "O'Brien's Sales", 0, 0, false, false, "'O''Brien''s Sales'!A1");
+        
+        cf = new CellReference("'Amazing!'!A1");
+        confirmCell(cf, "Amazing!", 0, 0, false, false, "'Amazing!'!A1");
     }
 
     
+    /* package */ static void confirmCell(CellReference cf, String expSheetName, int expRow, 
+            int expCol, boolean expIsRowAbs, boolean expIsColAbs, String expText) {
+        
+        assertEquals(expSheetName, cf.getSheetName());
+        assertEquals("row index is wrong", expRow, cf.getRow());
+        assertEquals("col index is wrong", expCol, cf.getCol());
+        assertEquals("isRowAbsolute is wrong", expIsRowAbs, cf.isRowAbsolute());
+        assertEquals("isColAbsolute is wrong", expIsColAbs, cf.isColAbsolute());
+        assertEquals("text is wrong", expText, cf.formatAsString());
+    }
+
     public static void main(String [] args) {
         System.out.println("Testing org.apache.poi.hssf.util.TestCellReference");
         junit.textui.TestRunner.run(TestCellReference.class);
     }
-    
 }
diff --git a/src/testcases/org/apache/poi/poifs/AllPOIFSTests.java b/src/testcases/org/apache/poi/poifs/AllPOIFSTests.java
new file mode 100755 (executable)
index 0000000..e302f17
--- /dev/null
@@ -0,0 +1,41 @@
+/* ====================================================================
+   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;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+import org.apache.poi.poifs.eventfilesystem.TestPOIFSReaderRegistry;
+import org.apache.poi.poifs.filesystem.AllPOIFSFileSystemTests;
+import org.apache.poi.poifs.property.AllPOIFSPropertyTests;
+import org.apache.poi.poifs.storage.AllPOIFSStorageTests;
+/**
+ * Test suite for all sub-packages of org.apache.poi.poifs
+ * 
+ * @author Josh Micich
+ */
+public final class AllPOIFSTests {
+    public static Test suite() {
+        TestSuite result = new TestSuite("Tests for org.apache.poi.poifs");
+        result.addTestSuite(TestPOIFSReaderRegistry.class);
+        result.addTest(AllPOIFSFileSystemTests.suite());
+        result.addTest(AllPOIFSPropertyTests.suite());
+        result.addTest(AllPOIFSStorageTests.suite());
+        return result;
+    }
+}
diff --git a/src/testcases/org/apache/poi/poifs/data/excel_with_embeded.xls b/src/testcases/org/apache/poi/poifs/data/excel_with_embeded.xls
new file mode 100644 (file)
index 0000000..ca2d403
Binary files /dev/null and b/src/testcases/org/apache/poi/poifs/data/excel_with_embeded.xls differ
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 (file)
index 0000000..72e7232
Binary files /dev/null and b/src/testcases/org/apache/poi/poifs/data/ppt_with_embeded.xls differ
diff --git a/src/testcases/org/apache/poi/poifs/data/visio_with_embeded.xls b/src/testcases/org/apache/poi/poifs/data/visio_with_embeded.xls
new file mode 100644 (file)
index 0000000..91c306f
Binary files /dev/null and b/src/testcases/org/apache/poi/poifs/data/visio_with_embeded.xls differ
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 (file)
index 0000000..d9fea29
Binary files /dev/null and b/src/testcases/org/apache/poi/poifs/data/word_with_embeded.doc differ
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 (executable)
index 0000000..2a0319a
--- /dev/null
@@ -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<br/>
+ * 
+ * @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;
+    }
+}
index 4f67f9876729a3f090b96c042f9d5d64e057c15c..870d752522a150be84606b0aae61ce166348ad2e 100644 (file)
@@ -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 (executable)
index 0000000..dbc5401
--- /dev/null
@@ -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 (executable)
index 0000000..a5459ed
--- /dev/null
@@ -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<br/>
+ * 
+ * @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 (executable)
index 0000000..8c15d38
--- /dev/null
@@ -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<br/>
+ * 
+ * @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;
+    }
+}
index 1473fa82ea1cafad887598259a1a5dc261251abf..4c84f04b71a884d710e863f547fd7b8031bc040b 100644 (file)
@@ -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()
+            );
         }
     }
     
index ee63825e2496e149c83ba8f85707877e4bc1a6c4..ac6fc08c052a503013a8ad0a04ab267c5fc19a11 100644 (file)
@@ -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 (executable)
index 0000000..bb6d382
--- /dev/null
@@ -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<br/>
+ * 
+ * @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 (file)
index 0000000..7efbfac
--- /dev/null
@@ -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));
+       }
+}