]> source.dussan.org Git - poi.git/commitdiff
Merged revisions 615190-618235 via svnmerge from
authorUgo Cei <ugo@apache.org>
Mon, 4 Feb 2008 16:55:43 +0000 (16:55 +0000)
committerUgo Cei <ugo@apache.org>
Mon, 4 Feb 2008 16:55:43 +0000 (16:55 +0000)
https://svn.apache.org/repos/asf/poi/trunk

........
  r615190 | nick | 2008-01-25 12:52:39 +0100 (Fri, 25 Jan 2008) | 1 line

  Correctly handle the last paragraph via a fix to TableCell - patch from bug #44292
........
  r615255 | nick | 2008-01-25 17:15:49 +0100 (Fri, 25 Jan 2008) | 1 line

  Don't swap AreaPtg references from relative to absolute, by correctly processing the fields. Patch from bug #44293
........
  r615259 | nick | 2008-01-25 17:33:59 +0100 (Fri, 25 Jan 2008) | 1 line

  Add a test to show the bug #42618 appears to be incorrect
........
  r615310 | yegor | 2008-01-25 20:27:56 +0100 (Fri, 25 Jan 2008) | 1 line

  commented failing test42618()
........
  r615315 | yegor | 2008-01-25 20:37:22 +0100 (Fri, 25 Jan 2008) | 1 line

  fix bug #44296: HSLF Not Extracting Slide Background Image
........
  r615610 | yegor | 2008-01-27 15:55:32 +0100 (Sun, 27 Jan 2008) | 1 line

  fix bug #44297: IntPtg must operate with unsigned short. Reading signed short results in incorrect formula calculation.
........
  r615769 | yegor | 2008-01-28 09:53:19 +0100 (Mon, 28 Jan 2008) | 1 line

  start a new POI 3.1 section in the change log
........
  r615859 | nick | 2008-01-28 13:18:12 +0100 (Mon, 28 Jan 2008) | 1 line

  Mostly fix bug 42618 (really this time...) - can now open the file properly, but getCellFormula() is still playing up (bug #44306 opened for this)
........
  r617156 | nick | 2008-01-31 17:41:53 +0100 (Thu, 31 Jan 2008) | 1 line

  Lots of documentation updates, to make it clearer how the code actually works
........
  r617167 | nick | 2008-01-31 18:30:16 +0100 (Thu, 31 Jan 2008) | 1 line

  Convert HSSFEventFactory to using the new HSSFRecordStream, which returns fully-formed HSSFRecords. HSSFRecordStream allows for pull-style eventusermodel processing
........
  r617483 | nick | 2008-02-01 13:13:08 +0100 (Fri, 01 Feb 2008) | 1 line

  Tweak the javadoc so it's clearer on the overview what the getFormat method does
........
  r617487 | nick | 2008-02-01 13:29:38 +0100 (Fri, 01 Feb 2008) | 1 line

  Improvements to how SystemOutLogger and CommonsLogger log messages with exceptions, and avoid an infinite loop with certain log messages with exceptions - triggered by bug #44326
........
  r617491 | nick | 2008-02-01 14:02:06 +0100 (Fri, 01 Feb 2008) | 1 line

  Patch from bug #44336 - correctly escape sheet names in formula references, including tests for this, and fixes to old tests that were expecting the un-escaped sheet names
........
  r617516 | nick | 2008-02-01 16:20:55 +0100 (Fri, 01 Feb 2008) | 1 line

  Make a start on the hyperlink record support - not finished yet though, so not enabled
........
  r617523 | nick | 2008-02-01 16:41:32 +0100 (Fri, 01 Feb 2008) | 1 line

  Get the Hyperlink record code so that it doesn't break any existing tests, and add in (no usermodel support yet though)
........
  r617555 | nick | 2008-02-01 17:52:58 +0100 (Fri, 01 Feb 2008) | 1 line

  More Hyperlink support. Doesn't end up in HSSFCell just yet, as the records are in the wrong bit of the file, so don't get associated with the sheet. All tests still passing though
........
  r617834 | yegor | 2008-02-02 18:06:14 +0100 (Sat, 02 Feb 2008) | 1 line

  usermodel support for excel hyperlinks
........
  r618230 | nick | 2008-02-04 11:48:29 +0100 (Mon, 04 Feb 2008) | 1 line

  Implement CountA, CountIf, Index, Rows and Columns functions. Patch from Josh Micich in bug #44345
........
  r618235 | nick | 2008-02-04 12:14:49 +0100 (Mon, 04 Feb 2008) | 1 line

  Test file with hyperlinks on many sheets, of different types
........

git-svn-id: https://svn.apache.org/repos/asf/poi/branches/ooxml@618325 13f79535-47bb-0310-9956-ffa450edef68

68 files changed:
build.xml
src/documentation/content/xdocs/changes.xml
src/documentation/content/xdocs/status.xml
src/java/org/apache/poi/hssf/dev/BiffViewer.java
src/java/org/apache/poi/hssf/eventusermodel/HSSFEventFactory.java
src/java/org/apache/poi/hssf/eventusermodel/HSSFRecordStream.java [new file with mode: 0644]
src/java/org/apache/poi/hssf/model/Workbook.java
src/java/org/apache/poi/hssf/record/HyperlinkRecord.java [new file with mode: 0644]
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/formula/Area3DPtg.java
src/java/org/apache/poi/hssf/record/formula/AreaPtg.java
src/java/org/apache/poi/hssf/record/formula/ConcatPtg.java
src/java/org/apache/poi/hssf/record/formula/IntPtg.java
src/java/org/apache/poi/hssf/record/formula/Ptg.java
src/java/org/apache/poi/hssf/record/formula/RangePtg.java
src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java
src/java/org/apache/poi/hssf/record/formula/SheetNameFormatter.java [new file with mode: 0755]
src/java/org/apache/poi/hssf/record/formula/UnknownPtg.java
src/java/org/apache/poi/hssf/usermodel/HSSFCell.java
src/java/org/apache/poi/hssf/usermodel/HSSFDataFormat.java
src/java/org/apache/poi/hssf/usermodel/HSSFHyperlink.java [new file with mode: 0755]
src/java/org/apache/poi/util/CommonsLogger.java
src/java/org/apache/poi/util/POILogger.java
src/java/org/apache/poi/util/SystemOutLogger.java
src/scratchpad/src/org/apache/poi/hslf/model/Fill.java
src/scratchpad/src/org/apache/poi/hslf/model/Picture.java
src/scratchpad/src/org/apache/poi/hslf/model/Shape.java
src/scratchpad/src/org/apache/poi/hslf/model/Slide.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Columns.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Counta.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Countif.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Index.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Rows.java
src/scratchpad/src/org/apache/poi/hwpf/usermodel/TableRow.java
src/scratchpad/testcases/org/apache/poi/hslf/data/44296.ppt [new file with mode: 0755]
src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestBugs.java
src/scratchpad/testcases/org/apache/poi/hssf/data/44297.xls [new file with mode: 0755]
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java [new file with mode: 0755]
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/EvalFactory.java [new file with mode: 0755]
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/NumericFunctionInvoker.java [new file with mode: 0755]
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestCountFuncs.java [new file with mode: 0755]
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestIndex.java [new file with mode: 0755]
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestRowCol.java [new file with mode: 0755]
src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestBug44297.java [new file with mode: 0755]
src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestFormulaEvaluatorDocs.java
src/scratchpad/testcases/org/apache/poi/hwpf/data/Bug44292.doc [new file with mode: 0644]
src/scratchpad/testcases/org/apache/poi/hwpf/usermodel/TestProblems.java
src/testcases/org/apache/poi/hssf/HSSFTests.java
src/testcases/org/apache/poi/hssf/data/HyperlinksOnManySheets.xls [new file with mode: 0755]
src/testcases/org/apache/poi/hssf/data/SimpleWithChoose.xls [new file with mode: 0755]
src/testcases/org/apache/poi/hssf/data/TestDataValidation.xls
src/testcases/org/apache/poi/hssf/data/WithHyperlink.xls [new file with mode: 0644]
src/testcases/org/apache/poi/hssf/data/WithTwoHyperLinks.xls [new file with mode: 0644]
src/testcases/org/apache/poi/hssf/eventusermodel/TestHSSFEventFactory.java
src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java
src/testcases/org/apache/poi/hssf/record/TestFormulaRecord.java
src/testcases/org/apache/poi/hssf/record/TestHyperlinkRecord.java [new file with mode: 0644]
src/testcases/org/apache/poi/hssf/record/formula/AbstractPtgTestCase.java
src/testcases/org/apache/poi/hssf/record/formula/AllFormulaTests.java [new file with mode: 0644]
src/testcases/org/apache/poi/hssf/record/formula/TestArea3DPtg.java [new file with mode: 0644]
src/testcases/org/apache/poi/hssf/record/formula/TestAreaPtg.java [new file with mode: 0644]
src/testcases/org/apache/poi/hssf/record/formula/TestFuncPtg.java [changed mode: 0755->0644]
src/testcases/org/apache/poi/hssf/record/formula/TestRef3DPtg.java [new file with mode: 0644]
src/testcases/org/apache/poi/hssf/record/formula/TestSheetNameFormatter.java [new file with mode: 0644]
src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java
src/testcases/org/apache/poi/hssf/usermodel/TestHSSFCell.java
src/testcases/org/apache/poi/hssf/usermodel/TestNamedRange.java

index 4e33434f4f955c0a02bf374a32ff021f83741fbe..8905dc13f57361016fdb3575ba5bdc879a65751b 100644 (file)
--- a/build.xml
+++ b/build.xml
@@ -167,7 +167,7 @@ under the License.
   <property name="mavendist.poi.dir" location="build/maven-dist/poi"/>
   <property name="mavendist.oap.dir" location="build/maven-dist/org.apache.poi"/>
   <property name="jar.name" value="poi"/>
-  <property name="version.id" value="3.0.2-beta1"/>
+  <property name="version.id" value="3.1-alpha1"/>
   <property name="halt.on.test.failure" value="true"/>
 
   <property name="jdk.version.source" value="1.5"
@@ -279,6 +279,7 @@ under the License.
         <antcall target="with.clover"/>
         
         <mkdir dir="build"/>
+        <mkdir dir="build/non-ant-classes"/>
         <mkdir dir="${main.output.dir}"/>
         <mkdir dir="${main14.output.dir}"/>
         <mkdir dir="${scratchpad.output.dir}"/>
index 51aeb22461679d232a938b021a28d59ad1c724cb..df0e5e51adb43f1269e256acfa95fec69e241617 100644 (file)
     </devs>
 
                <!-- Don't forget to update status.xml too! -->
-        <release version="3.0.2-FINAL" date="2008-??-??">
+        <release version="3.1-beta1" date="2008-??-??">
+           <action dev="POI-DEVELOPERS" type="fix">44345 - Implement CountA, CountIf, Index, Rows and Columns functions</action>
+           <action dev="POI-DEVELOPERS" type="fix">44336 - Properly escape sheet names as required when figuring out the text of formulas</action>
+           <action dev="POI-DEVELOPERS" type="add">44326 - Improvements to how SystemOutLogger and CommonsLogger log messages with exceptions, and avoid an infinite loop with certain log messages with exceptions</action>
+           <action dev="POI-DEVELOPERS" type="add">Support for a completed Record based "pull" stream, via org.apache.poi.hssf.eventusermodel.HSSFRecordStream, to complement the existing "push" Event User Model listener stuff</action>
+        </release>
+        <release version="3.0.2-FINAL" date="2008-02-04">
+            <action dev="POI-DEVELOPERS" type="fix">44297 - IntPtg must operate with unsigned short. Reading signed short results in incorrect formula calculation</action>
+            <action dev="POI-DEVELOPERS" type="fix">44296 - Fix for reading slide background images</action>
+            <action dev="POI-DEVELOPERS" type="fix">44293 - Avoid swapping AreaPtgs from relative to absolute</action>
+            <action dev="POI-DEVELOPERS" type="fix">44292 - Correctly process the last paragraph in a word file</action>
             <action dev="POI-DEVELOPERS" type="fix">44254 - Avoid some unread byte warnings, and properly understand DVALRecord</action>
             <action dev="POI-DEVELOPERS" type="add">Add another formula evaluation method, evaluateFormulaCell(cell), which will re-calculate the value for a formula, without affecting the formula itself.</action>
             <action dev="POI-DEVELOPERS" type="fix">41726 - Fix how we handle signed cell offsets in relative areas and references</action>
index feabdf76b40b9917fd6a8f0ebb4ed7a973c303d8..9a7a3f43cba804cbdc9286518442ad90b77920ad 100644 (file)
 
        <!-- Don't forget to update changes.xml too! -->
     <changes>
-        <release version="3.0.2-FINAL" date="2008-??-??">
+        <release version="3.1-beta1" date="2008-??-??">
+           <action dev="POI-DEVELOPERS" type="fix">44345 - Implement CountA, CountIf, Index, Rows and Columns functions</action>
+           <action dev="POI-DEVELOPERS" type="fix">44336 - Properly escape sheet names as required when figuring out the text of formulas</action>
+           <action dev="POI-DEVELOPERS" type="add">44326 - Improvements to how SystemOutLogger and CommonsLogger log messages with exceptions, and avoid an infinite loop with certain log messages with exceptions</action>
+           <action dev="POI-DEVELOPERS" type="add">Support for a completed Record based "pull" stream, via org.apache.poi.hssf.eventusermodel.HSSFRecordStream, to complement the existing "push" Event User Model listener stuff</action>
+        </release>
+        <release version="3.0.2-FINAL" date="2008-02-04">
+            <action dev="POI-DEVELOPERS" type="fix">44297 - IntPtg must operate with unsigned short. Reading signed short results in incorrect formula calculation</action>
+            <action dev="POI-DEVELOPERS" type="fix">44296 - Fix for reading slide background images</action>
+            <action dev="POI-DEVELOPERS" type="fix">44293 - Avoid swapping AreaPtgs from relative to absolute</action>
+            <action dev="POI-DEVELOPERS" type="fix">44292 - Correctly process the last paragraph in a word file</action>
             <action dev="POI-DEVELOPERS" type="fix">44254 - Avoid some unread byte warnings, and properly understand DVALRecord</action>
             <action dev="POI-DEVELOPERS" type="add">Add another formula evaluation method, evaluateFormulaCell(cell), which will re-calculate the value for a formula, without affecting the formula itself.</action>
             <action dev="POI-DEVELOPERS" type="fix">41726 - Fix how we handle signed cell offsets in relative areas and references</action>
index 2e343012d469597558c90dd90eab7ba64af797e8..242a85f45f8e4bf1275d8ceb8a444be51ce41e60 100644 (file)
@@ -515,6 +515,9 @@ public class BiffViewer {
             case FileSharingRecord.sid:
                 retval = new FileSharingRecord( in );
                 break;
+            case HyperlinkRecord.sid:
+                retval = new HyperlinkRecord( in );
+                break;
             default:
                 retval = new UnknownRecord( in );
         }
index 70c989c17974bd31f4804e1daf538a07d3943405..f8e64c928e8fb4f9d7fbab41e3d0cb41e3696acd 100644 (file)
@@ -129,109 +129,25 @@ public class HSSFEventFactory
        protected short genericProcessEvents(HSSFRequest req, RecordInputStream in)
                throws IOException, HSSFUserException
        {
+               boolean going = true;
                short userCode = 0;
-
-               short sid = 0;
-               process:
-               {
-                  
-                       Record rec       = null;
-                       Record lastRec   = null;
-                       DrawingRecord lastDrawingRecord = new DrawingRecord();
-
-                       while (in.hasNextRecord())
-                       {
-                               in.nextRecord();
-                               sid = in.getSid();;
-
-                //
-                // for some reasons we have to make the workbook to be at least 4096 bytes
-                // but if we have such workbook we fill the end of it with zeros (many zeros)
-                //
-                // it is not good:
-                // if the length( all zero records ) % 4 = 1
-                // e.g.: any zero record would be readed as  4 bytes at once ( 2 - id and 2 - size ).
-                // And the last 1 byte will be readed WRONG ( the id must be 2 bytes )
-                //
-                // So we should better to check if the sid is zero and not to read more data
-                // The zero sid shows us that rest of the stream data is a fake to make workbook 
-                // certain size
-                //
-                if ( sid == 0 )
-                    break;
-
-
-                               if ((rec != null) && (sid != ContinueRecord.sid))
-                               {
-                                       userCode = req.processRecord(rec);
-                                       if (userCode != 0) break process;
-                               }
-                               if (sid != ContinueRecord.sid)
-                               {
-                                        //System.out.println("creating "+sid);
-                                       Record[] recs = RecordFactory.createRecord(in);
-
-                                       if (recs.length > 1)
-                                       {                                // we know that the multiple
-                                               for (int k = 0; k < (recs.length - 1); k++)
-                                               {                            // record situations do not
-                                                       userCode = req.processRecord(
-                                                               recs[ k ]);          // contain continue records
-                                                       if (userCode != 0) break process;
-                                               }
-                                       }
-                                       rec = recs[ recs.length - 1 ];   // regardless we'll process
-
-                                       // the last record as though
-                                       // it might be continued
-                                       // if there is only one
-                                       // records, it will go here too.
-                               }
-                               else {
-                                       // Normally, ContinueRecords are handled internally
-                                       // However, in a few cases, there is a gap between a record at
-                                       //  its Continue, so we have to handle them specially
-                                       // This logic is much like in RecordFactory.createRecords()
-                                       Record[] recs = RecordFactory.createRecord(in);
-                                       ContinueRecord crec = (ContinueRecord)recs[0];
-                                       if((lastRec instanceof ObjRecord) || (lastRec instanceof TextObjectRecord)) {
-                                               // You can have Obj records between a DrawingRecord
-                                               //  and its continue!
-                                               lastDrawingRecord.processContinueRecord( crec.getData() );
-                                               // Trigger them on the drawing record, now it's complete
-                                               rec = lastDrawingRecord;
-                                       }
-                                       else if((lastRec instanceof DrawingGroupRecord)) {
-                                               ((DrawingGroupRecord)lastRec).processContinueRecord(crec.getData());
-                                               // Trigger them on the drawing record, now it's complete
-                                               rec = lastRec;
-                                       }
-                                       else {
-                        if (rec instanceof UnknownRecord) {
-                            ;//silently skip records we don't know about
-                        } else {
-                                                   throw new RecordFormatException("Records should handle ContinueRecord internally. Should not see this exception");
-                        }
-                                       }
-                               }
-
-                               // Update our tracking of the last record
-                               lastRec = rec;
-                               if(rec instanceof DrawingRecord) {
-                                       lastDrawingRecord = (DrawingRecord)rec;
-                               }
-                       }
-                       if (rec != null)
-                       {
-                               userCode = req.processRecord(rec);
-                               if (userCode != 0) break process;
+               Record r = null;
+               
+               // Create a new RecordStream and use that
+               HSSFRecordStream recordStream = new HSSFRecordStream(in);
+               
+               // Process each record as they come in
+               while(going) {
+                       r = recordStream.nextRecord();
+                       if(r != null) {
+                               userCode = req.processRecord(r);
+                               if (userCode != 0) break;
+                       } else {
+                               going = false;
                        }
                }
                
+               // All done, return our last code
                return userCode;
-
-               // Record[] retval = new Record[ records.size() ];
-               // retval = ( Record [] ) records.toArray(retval);
-               // return null;
     }
 }
diff --git a/src/java/org/apache/poi/hssf/eventusermodel/HSSFRecordStream.java b/src/java/org/apache/poi/hssf/eventusermodel/HSSFRecordStream.java
new file mode 100644 (file)
index 0000000..feb7a36
--- /dev/null
@@ -0,0 +1,234 @@
+/* ====================================================================
+   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.eventusermodel;
+
+import java.util.Vector;
+
+import org.apache.poi.hssf.record.ContinueRecord;
+import org.apache.poi.hssf.record.DrawingGroupRecord;
+import org.apache.poi.hssf.record.DrawingRecord;
+import org.apache.poi.hssf.record.ObjRecord;
+import org.apache.poi.hssf.record.Record;
+import org.apache.poi.hssf.record.RecordFactory;
+import org.apache.poi.hssf.record.RecordFormatException;
+import org.apache.poi.hssf.record.RecordInputStream;
+import org.apache.poi.hssf.record.TextObjectRecord;
+import org.apache.poi.hssf.record.UnknownRecord;
+
+/**
+ * A stream based way to get at complete records, with
+ *  as low a memory footprint as possible.
+ * This handles reading from a RecordInputStream, turning
+ *  the data into full records, processing continue records
+ *  etc.
+ * Most users should use {@link HSSFEventFactory} /
+ *  {@link HSSFListener} and have new records pushed to
+ *  them, but this does allow for a "pull" style of coding.  
+ */
+public class HSSFRecordStream {
+       private RecordInputStream in;
+
+       /** Have we run out of records on the stream? */
+       private boolean hitEOS = false;
+       /** Have we returned all the records there are? */
+       private boolean complete = false;
+       
+       /**
+        * Sometimes we end up with a bunch of
+        *  records. When we do, these should
+        *  be returned before the next normal
+        *  record processing occurs (i.e. before
+        *  we check for continue records and
+        *  return rec)
+        */
+       private Vector bonusRecords = null;
+       
+       /** 
+        * The next record to return, which may need to have its
+        *  continue records passed to it before we do
+        */
+       private Record rec = null;
+       /**
+        * The most recent record that we gave to the user
+        */
+       private Record lastRec = null;
+       /**
+        * The most recent DrawingRecord seen
+        */
+       private DrawingRecord lastDrawingRecord = new DrawingRecord();
+       
+       public HSSFRecordStream(RecordInputStream inp) {
+               this.in = inp;
+       }
+
+       /**
+        * Returns the next (complete) record from the 
+        *  stream, or null if there are no more.
+        */
+       public Record nextRecord() {
+               Record r = null;
+               
+               // Loop until we get something
+               while(r == null && !complete) {
+                       // Are there any bonus records that we need to
+                       //  return?
+                       r = getBonusRecord();
+                       
+                       // If not, ask for the next real record
+                       if(r == null) {
+                               r = getNextRecord();
+                       }
+               }
+               
+               // All done
+               return r;
+       }
+       
+       /**
+        * If there are any "bonus" records, that should
+        *  be returned before processing new ones, 
+        *  grabs the next and returns it.
+        * If not, returns null;
+        */
+       private Record getBonusRecord() {
+               if(bonusRecords != null) {
+                       Record r = (Record)bonusRecords.remove(0);
+                       if(bonusRecords.size() == 0) {
+                               bonusRecords = null;
+                       }
+                       return r;
+               }
+               return null;
+       }
+       
+       /**
+        * Returns the next available record, or null if
+        *  this pass didn't return a record that's
+        *  suitable for returning (eg was a continue record).
+        */
+       private Record getNextRecord() {
+               Record toReturn = null;
+               
+               if(in.hasNextRecord()) {
+                       // Grab our next record
+                       in.nextRecord();
+                       short sid = in.getSid();
+                       
+            //
+            // for some reasons we have to make the workbook to be at least 4096 bytes
+            // but if we have such workbook we fill the end of it with zeros (many zeros)
+            //
+            // it is not good:
+            // if the length( all zero records ) % 4 = 1
+            // e.g.: any zero record would be readed as  4 bytes at once ( 2 - id and 2 - size ).
+            // And the last 1 byte will be readed WRONG ( the id must be 2 bytes )
+            //
+            // So we should better to check if the sid is zero and not to read more data
+            // The zero sid shows us that rest of the stream data is a fake to make workbook 
+            // certain size
+            //
+            if ( sid == 0 )
+                return null;
+
+
+            // If we had a last record, and this one
+            //  isn't a continue record, then pass
+            //  it on to the listener
+                       if ((rec != null) && (sid != ContinueRecord.sid))
+                       {
+                               // This last record ought to be returned
+                               toReturn = rec;
+                       }
+                       
+                       // If this record isn't a continue record,
+                       //  then build it up
+                       if (sid != ContinueRecord.sid)
+                       {
+                               //System.out.println("creating "+sid);
+                               Record[] recs = RecordFactory.createRecord(in);
+
+                               // We know that the multiple record situations
+                               //  don't contain continue records, so just
+                               //  pass those on to the listener now
+                               if (recs.length > 1) {
+                                       bonusRecords = new Vector(recs.length-1);
+                                       for (int k = 0; k < (recs.length - 1); k++)     {
+                                               bonusRecords.add(recs[k]);
+                                       }
+                               }
+                               
+                               // Regardless of the number we created, always hold
+                               //  onto the last record to be processed on the next
+                               //  loop, in case it has any continue records
+                               rec = recs[ recs.length - 1 ];
+                               // Don't return it just yet though, as we probably have
+                               //  a record from the last round to return
+                       }
+                       else {
+                               // Normally, ContinueRecords are handled internally
+                               // However, in a few cases, there is a gap between a record at
+                               //  its Continue, so we have to handle them specially
+                               // This logic is much like in RecordFactory.createRecords()
+                               Record[] recs = RecordFactory.createRecord(in);
+                               ContinueRecord crec = (ContinueRecord)recs[0];
+                               if((lastRec instanceof ObjRecord) || (lastRec instanceof TextObjectRecord)) {
+                                       // You can have Obj records between a DrawingRecord
+                                       //  and its continue!
+                                       lastDrawingRecord.processContinueRecord( crec.getData() );
+                                       // Trigger them on the drawing record, now it's complete
+                                       rec = lastDrawingRecord;
+                               }
+                               else if((lastRec instanceof DrawingGroupRecord)) {
+                                       ((DrawingGroupRecord)lastRec).processContinueRecord(crec.getData());
+                                       // Trigger them on the drawing record, now it's complete
+                                       rec = lastRec;
+                               }
+                               else {
+                    if (rec instanceof UnknownRecord) {
+                        ;//silently skip records we don't know about
+                    } else {
+                                           throw new RecordFormatException("Records should handle ContinueRecord internally. Should not see this exception");
+                    }
+                               }
+                       }
+
+                       // Update our tracking of the last record
+                       lastRec = rec;
+                       if(rec instanceof DrawingRecord) {
+                               lastDrawingRecord = (DrawingRecord)rec;
+                       }
+               } else {
+                       // No more records
+                       hitEOS = true;
+               }
+               
+               // If we've hit the end-of-stream, then
+               //  finish off the last record and be done
+               if(hitEOS) {
+                       complete = true;
+                       
+                       // Return the last record if there was
+                       //  one, otherwise null
+                       if(rec != null) {
+                               toReturn = rec;
+                               rec = null;
+                       }
+               }
+                       
+               return toReturn;
+       }
+}
\ No newline at end of file
index 08c236cda116ac9fcaf131f758c04abea98bf357..2ba50857ca6fbe39371e1c6aa3e40604e9b5b35b 100644 (file)
@@ -93,6 +93,8 @@ 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
     protected int              numfonts    = 0;   // hold the number of font records
@@ -133,7 +135,8 @@ public class Workbook implements Model
         Workbook  retval  = new Workbook();
         ArrayList records = new ArrayList(recs.size() / 3);
 
-        for (int k = 0; k < recs.size(); k++) {
+        int k;
+        for (k = 0; k < recs.size(); k++) {
             Record rec = ( Record ) recs.get(k);
 
             if (rec.getSid() == EOFRecord.sid) {
@@ -248,6 +251,17 @@ public class Workbook implements Model
         //            retval.records.supbookpos = retval.records.bspos + 1;
         //            retval.records.namepos    = retval.records.supbookpos + 1;
         //        }
+        
+        // Look for other interesting values that
+        //  follow the EOFRecord
+        for ( ; k < recs.size(); k++) {
+            Record rec = ( Record ) recs.get(k);
+            switch (rec.getSid()) {
+               case HyperlinkRecord.sid:
+                       retval.hyperlinks.add(rec);
+                       break;
+            }
+        }
 
         retval.records.setRecords(records);
         
@@ -2116,6 +2130,11 @@ public class Workbook implements Model
         return null;
     }
 
+    public List getHyperlinks()
+    {
+       return hyperlinks;
+    }
+    
     public List getRecords()
     {
         return records.getRecords();
diff --git a/src/java/org/apache/poi/hssf/record/HyperlinkRecord.java b/src/java/org/apache/poi/hssf/record/HyperlinkRecord.java
new file mode 100644 (file)
index 0000000..0dcd45a
--- /dev/null
@@ -0,0 +1,370 @@
+/* ====================================================================
+ Copyright 2002-2004   Apache Software Foundation
+
+ Licensed 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.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.StringUtil;
+
+/**
+ * The <code>HyperlinkRecord</code> wraps an HLINK-record 
+ *  from the Excel-97 format.
+ * Supports only external links for now (eg http://) 
+ *
+ * @author      Mark Hissink Muller <a href="mailto:mark@hissinkmuller.nl >mark&064;hissinkmuller.nl</a>
+ */
+public class HyperlinkRecord extends Record implements CellValueRecordInterface
+{
+    /** Indicates the URL in the Record */
+    private static byte[] GUID_OF_URL_MONIKER =
+    { -32, -55, -22, 121, -7, -70, -50, 17, -116, -126, 0, -86, 0, 75, -87, 11 };
+
+    /** Indicates the STD_LINK in the Record */
+    // MHM: to be added when necessary
+    private static byte[] GUID_OF_STD_LINK =  {};
+
+    /** Logger */
+    public static final Log log = LogFactory.getLog(HyperlinkRecord.class);
+
+    // quick and dirty
+    private static final boolean _DEBUG_ = true;
+
+    public final static short sid = 0x1b8;
+
+    private short field_1_unknown;
+    private int field_2_row;
+    private short field_3_column;
+    private short field_4_xf_index;
+    private byte[] field_5_unknown;
+    private int field_6_label_opts;
+    private int field_7_url_len;
+    private int field_8_label_len;
+    private String field_9_label;
+    private byte[] field_10_unknown;
+    private int field_11_url_opts;
+    private String field_12_url;
+
+    /** Blank Constructor */
+    public HyperlinkRecord()
+    {
+    }
+
+    /** Real Constructor */
+    public HyperlinkRecord(RecordInputStream in)
+    {
+        super(in);
+    }
+
+    /* (non-Javadoc)
+     * @see org.apache.poi.hssf.record.CellValueRecordInterface#getColumn()
+     */
+    public short getColumn()
+    {
+        return field_3_column;
+    }
+
+    /* (non-Javadoc)
+     * @see org.apache.poi.hssf.record.CellValueRecordInterface#getRow()
+     */
+    public int getRow()
+    {
+        return field_2_row;
+    }
+
+    /* (non-Javadoc)
+     * @see org.apache.poi.hssf.record.CellValueRecordInterface#getXFIndex()
+     */
+    public short getXFIndex()
+    {
+        return field_4_xf_index;
+    }
+
+    /* (non-Javadoc)
+     * @see org.apache.poi.hssf.record.CellValueRecordInterface#isAfter(org.apache.poi.hssf.record.CellValueRecordInterface)
+     */
+    public boolean isAfter(CellValueRecordInterface i)
+    {
+        if (this.getRow() < i.getRow())
+        {
+            return false;
+        }
+        if ((this.getRow() == i.getRow()) && (this.getColumn() < i.getColumn()))
+        {
+            return false;
+        }
+        if ((this.getRow() == i.getRow()) && (this.getColumn() == i.getColumn()))
+        {
+            return false;
+        }
+        return true;
+    }
+
+    /* (non-Javadoc)
+     * @see org.apache.poi.hssf.record.CellValueRecordInterface#isBefore(org.apache.poi.hssf.record.CellValueRecordInterface)
+     */
+    public boolean isBefore(CellValueRecordInterface i)
+    {
+        if (this.getRow() > i.getRow())
+        {
+            return false;
+        }
+        if ((this.getRow() == i.getRow()) && (this.getColumn() > i.getColumn()))
+        {
+            return false;
+        }
+        if ((this.getRow() == i.getRow()) && (this.getColumn() == i.getColumn()))
+        {
+            return false;
+        }
+        return true;
+    }
+
+    /* (non-Javadoc)
+     * @see org.apache.poi.hssf.record.CellValueRecordInterface#isEqual(org.apache.poi.hssf.record.CellValueRecordInterface)
+     */
+    public boolean isEqual(CellValueRecordInterface i)
+    {
+        return ((this.getRow() == i.getRow()) && (this.getColumn() == i.getColumn()));
+    }
+
+    /* (non-Javadoc)
+     * @see org.apache.poi.hssf.record.CellValueRecordInterface#setColumn(short)
+     */
+    public void setColumn(short col)
+    {
+        this.field_3_column = col;
+
+    }
+
+    /* (non-Javadoc)
+     * @see org.apache.poi.hssf.record.CellValueRecordInterface#setRow(int)
+     */
+    public void setRow(int row)
+    {
+        this.field_2_row = row;
+
+    }
+
+    /* (non-Javadoc)
+     * @see org.apache.poi.hssf.record.CellValueRecordInterface#setXFIndex(short)
+     */
+    public void setXFIndex(short xf)
+    {
+        this.field_4_xf_index = xf;
+
+    }
+
+    /**
+     * @param in the RecordInputstream to read the record from
+     */
+    protected void fillFields(RecordInputStream in)
+    {
+//     System.err.println(in.currentSid);
+//     System.err.println(in.currentLength);
+//     for(int i=0; i<300; i++) {
+//             System.err.println(in.readByte());
+//     }
+//     if(1==1)
+//             throw new IllegalArgumentException("");
+       
+        field_1_unknown = in.readShort();
+        field_2_row = in.readUShort(); 
+        field_3_column = in.readShort();
+        field_4_xf_index = in.readShort();
+        
+        // Next up is 16 bytes we don't get
+        field_5_unknown = new byte[16];
+        try {
+        in.read(field_5_unknown);
+        } catch(IOException e) { throw new IllegalStateException(e.getMessage()); }
+        
+        // Some sort of opts
+        field_6_label_opts = in.readInt();
+        
+        // Now for lengths, in characters
+        field_7_url_len = in.readInt();
+        field_8_label_len = in.readInt();
+        
+        // Now we have the label, as little endian unicode,
+        //  with a trailing \0
+        field_9_label = in.readUnicodeLEString(field_8_label_len);
+        
+        // Next up is some more data we can't make sense of
+        field_10_unknown = new byte[16];
+        try {
+        in.read(field_10_unknown);
+        } catch(IOException e) { throw new IllegalStateException(e.getMessage()); }
+        
+        // Might need to nudge the length by one byte
+        // This is an empirical hack!
+        field_11_url_opts = in.readInt();
+        if(field_11_url_opts == 44) {
+               field_7_url_len--;
+        }
+        
+        // Finally it's the URL
+        int strlen = field_7_url_len > (in.remaining()/2) ? (in.remaining()/2) : field_7_url_len;
+        field_12_url = in.readUnicodeLEString(strlen);
+    }
+    
+    /* (non-Javadoc)
+     * @see org.apache.poi.hssf.record.Record#getSid()
+     */
+    public short getSid()
+    {
+        return HyperlinkRecord.sid;
+    }
+
+    protected void validateSid(short id)
+    {
+        if (id != sid)
+        {
+            throw new RecordFormatException("NOT A HYPERLINKRECORD!");
+        }
+    }
+
+    public int serialize(int offset, byte[] data)
+    {
+        LittleEndian.putShort(data, 0 + offset, sid);
+        LittleEndian.putShort(data, 2 + offset,
+                              ( short )(getRecordSize()-4));
+        LittleEndian.putShort(data, 4 + offset, field_1_unknown);
+        LittleEndian.putUShort(data, 6 + offset, field_2_row);
+        LittleEndian.putShort(data, 8 + offset, field_3_column);
+        LittleEndian.putShort(data, 10 + offset, field_4_xf_index);
+        
+        offset += 12;
+        for(int i=0; i<field_5_unknown.length; i++) {
+               data[offset] = field_5_unknown[i];
+               offset++;
+        }
+        
+        LittleEndian.putInt(data, offset, field_6_label_opts);
+        offset += 4;
+        LittleEndian.putInt(data, offset, field_7_url_len);
+        offset += 4;
+        LittleEndian.putInt(data, offset, field_8_label_len);
+        offset += 4;
+        StringUtil.putUnicodeLE(field_9_label, data, offset);
+        offset += field_9_label.length()*2;
+
+        for(int i=0; i<field_10_unknown.length; i++) {
+               data[offset] = field_10_unknown[i];
+               offset++;
+        }
+       
+        LittleEndian.putInt(data, offset, field_11_url_opts);
+        offset += 4;
+        StringUtil.putUnicodeLE(field_12_url, data, offset);
+        
+       return getRecordSize();
+    }
+
+    public int getRecordSize()
+    {
+       // We have:
+       // 4 shorts
+       // junk
+       // 3 ints
+       // label
+       // junk
+       // int
+       // url
+       return 4 + 4*2 + field_5_unknown.length +
+               3*4 + field_9_label.length()*2 +
+               field_10_unknown.length + 4 +
+               field_12_url.length()*2;
+    }
+
+    public String toString()
+    {
+        StringBuffer buffer = new StringBuffer();
+
+        buffer.append("[HYPERLINK RECORD]\n");
+        buffer.append("    .row            = ").append(Integer.toHexString(getRow())).append("\n");
+        buffer.append("    .column         = ").append(Integer.toHexString(getColumn())).append("\n");
+        buffer.append("    .xfindex        = ").append(Integer.toHexString(getXFIndex())).append("\n");
+        buffer.append("    .label          = ").append(field_9_label).append("\n");
+        buffer.append("    .url            = ").append(field_12_url).append("\n");
+        buffer.append("[/HYPERLINK RECORD]\n");
+        return buffer.toString();
+    }
+
+    /**
+     * @return Returns the label.
+     */
+    public String getLabel()
+    {
+       if(field_9_label.length() == 0) {
+               return "";
+       } else {
+               // Trim off \0
+            return field_9_label.substring(0, field_9_label.length() - 1);
+       }
+    }
+
+    /**
+     * @param label The label to set.
+     */
+    public void setLabel(String label)
+    {
+        this.field_9_label = label + '\u0000';
+        this.field_8_label_len = field_9_label.length();
+    }
+
+    /**
+     * @return Returns the Url.
+     */
+    public URL getUrl() throws MalformedURLException
+    {
+        return new URL(getUrlString());
+    }
+    public String getUrlString()
+    {
+       if(field_12_url.length() == 0) {
+               return "";
+       } else {
+               // Trim off \0
+            return field_12_url.substring(0, field_12_url.length() - 1);
+       }
+    }
+
+    /**
+     * @param url The url to set.
+     */
+    public void setUrl(URL url)
+    {
+       setUrl(url.toString());
+    }
+    /**
+     * @param url The url to set.
+     */
+    public void setUrl(String url)
+    {
+        this.field_12_url = url + '\u0000';
+        this.field_7_url_len = field_12_url.length();
+    }
+
+    public int getOptions(){
+        return field_11_url_opts;
+    }
+}
index 20e8ba788accecef918403373efaa982e34a37ee..0f164b44735ae4ac4c58df02af4dd2206bf37a97 100644 (file)
@@ -76,7 +76,8 @@ public class RecordFactory
                 WriteProtectRecord.class, FilePassRecord.class, PaneRecord.class,
                 NoteRecord.class, ObjectProtectRecord.class, ScenarioProtectRecord.class, 
                 FileSharingRecord.class, ChartTitleFormatRecord.class,
-                DVRecord.class, DVALRecord.class, UncalcedRecord.class
+                DVRecord.class, DVALRecord.class, UncalcedRecord.class,
+                HyperlinkRecord.class
             };
     }
     private static Map           recordsMap  = recordsToMap(records);
index dd853f2463904ea36293d51124c2e8a73b22cb43..32094287fa73f26725c2d7cac219cb6f30812163 100755 (executable)
@@ -249,7 +249,7 @@ public class RecordInputStream extends InputStream
    */  
   public String readUnicodeLEString(int length) {
     if ((length < 0) || (((remaining() / 2) < length) && !isContinueNext())) {
-            throw new IllegalArgumentException("Illegal length");
+            throw new IllegalArgumentException("Illegal length - asked for " + length + " but only " + (remaining()/2) + " left!");
     }
 
     StringBuffer buf = new StringBuffer(length);
index d808a94c4d63acfc67ca065846e35b0ea507642b..ac260ffa4eb318d7ebb77e6b97b252eddc5ed934 100644 (file)
@@ -261,13 +261,16 @@ public class Area3DPtg extends Ptg
                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.
+     */
        public String toFormulaString(Workbook book)
        {
-               SheetReferences refs = book == null ? null : book.getSheetReferences();
                StringBuffer retval = new StringBuffer();
-               if ( refs != null )
-               {
-                       retval.append( refs.getSheetName( this.field_1_index_extern_sheet ) );
+               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() );
index 32579a69f3d76c0d46284a4fb29265b0036781e9..908c8d5e3cce9bbcaf1619f98519cd79dee3b0d9 100644 (file)
@@ -43,9 +43,9 @@ public class AreaPtg
     private short             field_3_first_column;
     private short             field_4_last_column;
     
-    private BitField         rowRelative = BitFieldFactory.getInstance(0x8000);
-    private BitField         colRelative = BitFieldFactory.getInstance(0x4000);
-    private BitField         column      = BitFieldFactory.getInstance(0x3FFF);
+    private final static BitField   rowRelative = BitFieldFactory.getInstance(0x8000);
+    private final static BitField   colRelative = BitFieldFactory.getInstance(0x4000);
+    private final static BitField   columnMask      = BitFieldFactory.getInstance(0x3FFF);
 
     protected AreaPtg() {
       //Required for clone methods
@@ -157,7 +157,7 @@ public class AreaPtg
      */
     public short getFirstColumn()
     {
-        return column.getShortValue(field_3_first_column);
+        return columnMask.getShortValue(field_3_first_column);
     }
 
     /**
@@ -204,7 +204,7 @@ public class AreaPtg
      */
     public void setFirstColumn(short column)
     {
-        field_3_first_column = column;   // fixme
+       field_3_first_column=columnMask.setShortValue(field_3_first_column, column);
     }
 
     /**
@@ -220,7 +220,7 @@ public class AreaPtg
      */
     public short getLastColumn()
     {
-        return column.getShortValue(field_4_last_column);
+        return columnMask.getShortValue(field_4_last_column);
     }
 
     /**
@@ -269,7 +269,7 @@ public class AreaPtg
      */
     public void setLastColumn(short column)
     {
-        field_4_last_column = column;   // fixme
+       field_4_last_column=columnMask.setShortValue(field_4_last_column, column);
     }
 
     /**
index b5a2de2a5de324eb36c59077e2e3da6a5f39c191..41d2de0cbae58ca2f0147003ee663f47869148eb 100644 (file)
@@ -34,11 +34,10 @@ public class ConcatPtg
     public final static byte sid  = 0x08;
     
     private final static String CONCAT = "&";
-
+    
     public ConcatPtg(RecordInputStream in)
     {
-
-        // doesn't need anything
+       // No contents
     }
     
     public ConcatPtg() {
index 602289776b0e7647b091b04739ea44e8b97f3056..257c089df8b297f48e1687772a9825578409ebf8 100644 (file)
@@ -40,7 +40,7 @@ public class IntPtg
 {
     public final static int  SIZE = 3;
     public final static byte sid  = 0x1e;
-    private short            field_1_value;
+    private int            field_1_value;
   
     private IntPtg() {
       //Required for clone methods
@@ -48,42 +48,31 @@ public class IntPtg
 
     public IntPtg(RecordInputStream in)
     {
-        setValue(in.readShort());
+        setValue(in.readUShort());
     }
     
     
     // IntPtg should be able to create itself, shouldnt have to call setValue
     public IntPtg(String formulaToken) {
-        setValue(Short.parseShort(formulaToken));
+        setValue(Integer.parseInt(formulaToken));
     }
 
     /**
      * Sets the wrapped value.
      * Normally you should call with a positive int.
      */
-    public void setValue(short value)
-    {
-        field_1_value = value;
-    }
-
-    /**
-     * Sets the unsigned value.
-     * (Handles conversion to the internal short value) 
-     */
     public void setValue(int value)
     {
-       if(value > Short.MAX_VALUE) {
-               // Need to wrap
-               value -= (Short.MAX_VALUE+1)*2;
-       }
-       field_1_value = (short)value;
+        if(value < 0 || value > (Short.MAX_VALUE + 1)*2 )
+            throw new IllegalArgumentException("Unsigned short is out of range: " + value);
+        field_1_value = value;
     }
 
     /**
      * Returns the value as a short, which may have
      *  been wrapped into negative numbers
      */
-    public short getValue()
+    public int getValue()
     {
         return field_1_value;
     }
@@ -102,7 +91,7 @@ public class IntPtg
     public void writeBytes(byte [] array, int offset)
     {
         array[ offset + 0 ] = sid;
-        LittleEndian.putShort(array, offset + 1, getValue());
+        LittleEndian.putUShort(array, offset + 1, getValue());
     }
 
     public int getSize()
index cc9a4236ba438ff9efd92b1b4214f40e756510e2..f006509c23fcbffc116eb6d25300725cfa691e08 100644 (file)
@@ -137,8 +137,8 @@ public abstract class Ptg
                  break;
                  
              case DividePtg.sid :               // 0x06
-                                 retval = new DividePtg(in);
-                                 break;
+                     retval = new DividePtg(in);
+                     break;
                  
              case PowerPtg.sid :                // 0x07
                  retval = new PowerPtg(in);
@@ -208,6 +208,7 @@ public abstract class Ptg
                 break;
  
              case AttrPtg.sid :                 // 0x19
+             case 0x1a :
                  retval = new AttrPtg(in);
                                  break;
                  
@@ -224,8 +225,8 @@ public abstract class Ptg
                                  break;
  
              case NumberPtg.sid :               // 0x1f
-                                retval = new NumberPtg(in);
-                                break;
+                     retval = new NumberPtg(in);
+                     break;
                  
              case ArrayPtg.sid :                // 0x20
                retval = new ArrayPtg(in);
@@ -350,9 +351,12 @@ public abstract class Ptg
              case DeletedArea3DPtg.sid + 0x40 : // 0x7d
                  retval = new DeletedArea3DPtg(in);
                  break;
-
+                 
+             case 0x00:
+                retval = new UnknownPtg();
+                break;
+                 
             default :
-
                  //retval = new UnknownPtg();
                  throw new java.lang.UnsupportedOperationException(" Unknown Ptg in Formula: 0x"+
                         Integer.toHexString(( int ) id) + " (" + ( int ) id + ")");
index f0bd8c1c1b949a8535997b6239156548a4823f94..51df7844a4babd69afee4d7977c9a9f8c273d3cf 100644 (file)
@@ -25,22 +25,22 @@ import org.apache.poi.hssf.record.RecordInputStream;
  */
 public class RangePtg extends OperationPtg
 {
+    public final static int  SIZE = 1;
     public final static byte sid  = 0x11;
 
-
     public RangePtg()
     {
     }
 
     public RangePtg(RecordInputStream in)
     {
-        // doesn't need anything
+       // No contents
     }
 
 
     public int getSize()
     {
-        return 1;
+        return SIZE;
     }
 
     public void writeBytes( byte[] array, int offset )
index bc5430bb5d4c1a4835d6b4ca4b6db5f271c213f8..510eebb037e9ac2f4dab15a996d848e6284ad98e 100644 (file)
@@ -157,18 +157,31 @@ public class Ref3DPtg extends Ptg {
 
     }
 
-    public String toFormulaString(Workbook book) {
+    // TODO - find a home for this method
+    // There is already a method on Workbook called getSheetName but it seems to do something different.
+       static String getSheetName(Workbook book, int externSheetIndex) {
+        // TODO - there are 3 ways this method can return null. Is each valid?
+        if (book == null) {
+            return null;
+        }
+
+        SheetReferences refs = book.getSheetReferences();
+        if (refs == null) {
+            return null;
+        }
+        return refs.getSheetName(externSheetIndex);
+    }
+    /**
+     * @return text representation of this cell reference that can be used in text 
+     * formulas. The sheet name will get properly delimited if required.
+     */
+    public String toFormulaString(Workbook book)
+    {
         StringBuffer retval = new StringBuffer();
-        SheetReferences refs = book == null ? null : book.getSheetReferences();
-        if (refs != null) {
-               String sheetName =refs.getSheetName((int)this.field_1_index_extern_sheet);
-               boolean appendQuotes = sheetName.indexOf(" ") >= 0;
-               if (appendQuotes)
-                 retval.append("'");
-            retval.append(sheetName);
-               if (appendQuotes)
-                 retval.append("'");
-            retval.append('!');
+        String sheetName = getSheetName(book, field_1_index_extern_sheet);
+        if(sheetName != null) {
+            SheetNameFormatter.appendFormat(retval, sheetName);
+            retval.append( '!' );
         }
         retval.append((new CellReference(getRow(),getColumn(),!isRowRelative(),!isColRelative())).toString()); 
         return retval.toString();
diff --git a/src/java/org/apache/poi/hssf/record/formula/SheetNameFormatter.java b/src/java/org/apache/poi/hssf/record/formula/SheetNameFormatter.java
new file mode 100755 (executable)
index 0000000..ba796db
--- /dev/null
@@ -0,0 +1,245 @@
+/* ====================================================================
+   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.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Formats sheet names for use in formula expressions.
+ * 
+ * @author Josh Micich
+ */
+final class SheetNameFormatter {
+       
+       private static final String BIFF8_LAST_COLUMN = "IV";
+       private static final int BIFF8_LAST_COLUMN_TEXT_LEN = BIFF8_LAST_COLUMN.length();
+       private static final String BIFF8_LAST_ROW = String.valueOf(0x10000);
+       private static final int BIFF8_LAST_ROW_TEXT_LEN = BIFF8_LAST_ROW.length();
+
+       private static final char DELIMITER = '\'';
+       
+       private static final Pattern CELL_REF_PATTERN = Pattern.compile("([A-Za-z])+[0-9]+");
+
+       private SheetNameFormatter() {
+               // no instances of this class
+       }
+       /**
+        * Used to format sheet names as they would appear in cell formula expressions.
+        * @return the sheet name unchanged if there is no need for delimiting.  Otherwise the sheet
+        * name is enclosed in single quotes (').  Any single quotes which were already present in the 
+        * sheet name will be converted to double single quotes ('').  
+        */
+       public static String format(String rawSheetName) {
+               StringBuffer sb = new StringBuffer(rawSheetName.length() + 2);
+               appendFormat(sb, rawSheetName);
+               return sb.toString();
+       }
+       
+       /**
+        * Convenience method for when a StringBuffer is already available
+        * 
+        * @param out - sheet name will be appended here possibly with delimiting quotes 
+        */
+       public static void appendFormat(StringBuffer out, String rawSheetName) {
+               boolean needsQuotes = needsDelimiting(rawSheetName);
+               if(needsQuotes) {
+                       out.append(DELIMITER);
+                       appendAndEscape(out, rawSheetName);
+                       out.append(DELIMITER);
+               } else {
+                       out.append(rawSheetName);
+               }
+       }
+
+       private static void appendAndEscape(StringBuffer sb, String rawSheetName) {
+               int len = rawSheetName.length();
+               for(int i=0; i<len; i++) {
+                       char ch = rawSheetName.charAt(i);
+                       if(ch == DELIMITER) {
+                               // single quotes (') are encoded as ('')
+                               sb.append(DELIMITER);
+                       }
+                       sb.append(ch);
+               }
+       }
+
+       private static boolean needsDelimiting(String rawSheetName) {
+               int len = rawSheetName.length();
+               if(len < 1) {
+                       throw new RuntimeException("Zero length string is an invalid sheet name");
+               }
+               if(Character.isDigit(rawSheetName.charAt(0))) {
+                       // sheet name with digit in the first position always requires delimiting
+                       return true;
+               }
+               for(int i=0; i<len; i++) {
+                       char ch = rawSheetName.charAt(i);
+                       if(isSpecialChar(ch)) {
+                               return true;
+                       }
+               }
+               if(Character.isLetter(rawSheetName.charAt(0))
+                               && Character.isDigit(rawSheetName.charAt(len-1))) {
+                       // note - values like "A$1:$C$20" don't get this far 
+                       if(nameLooksLikePlainCellReference(rawSheetName)) {
+                               return true;
+                       }
+               }
+               return false;
+       }
+       
+    /**
+     * @return <code>true</code> if the presence of the specified character in a sheet name would 
+     * require the sheet name to be delimited in formulas.  This includes every non-alphanumeric 
+     * character besides underscore '_'.
+     */
+    /* package */ static boolean isSpecialChar(char ch) {
+        // note - Character.isJavaIdentifierPart() would allow dollars '$'
+        if(Character.isLetterOrDigit(ch)) {
+            return false;
+        }
+        switch(ch) {
+            case '_': // underscore is ok
+                return false;
+            case '\n':
+            case '\r':
+            case '\t':
+                throw new RuntimeException("Illegal character (0x" 
+                        + Integer.toHexString(ch) + ") found in sheet name");
+        }
+        return true;
+    }
+       
+
+       /**
+        * Used to decide whether sheet names like 'AB123' need delimiting due to the fact that they 
+        * look like cell references.
+        * <p/>
+        * This code is currently being used for translating formulas represented with <code>Ptg</code>
+        * tokens into human readable text form.  In formula expressions, a sheet name always has a 
+        * trailing '!' so there is little chance for ambiguity.  It doesn't matter too much what this 
+        * method returns but it is worth noting the likely consumers of these formula text strings:
+        * <ol>
+        * <li>POI's own formula parser</li>
+        * <li>Visual reading by human</li>
+        * <li>VBA automation entry into Excel cell contents e.g.  ActiveCell.Formula = "=c64!A1"</li>
+        * <li>Manual entry into Excel cell contents</li>
+        * <li>Some third party formula parser</li>
+        * </ol>
+        * 
+        * At the time of writing, POI's formula parser tolerates cell-like sheet names in formulas
+        * with or without delimiters.  The same goes for Excel(2007), both manual and automated entry.  
+        * <p/>
+        * For better or worse this implementation attempts to replicate Excel's formula renderer.
+        * Excel uses range checking on the apparent 'row' and 'column' components.  Note however that
+        * the maximum sheet size varies across versions:
+        * <p/>
+        * <blockquote><table border="0" cellpadding="1" cellspacing="0" 
+        *                 summary="Notable cases.">
+        *   <tr><th>Version&nbsp;&nbsp;</th><th>File Format&nbsp;&nbsp;</th>
+        *      <th>Last Column&nbsp;&nbsp;</th><th>Last Row</th></tr>
+        *   <tr><td>97-2003</td><td>BIFF8</td><td>"IV" (2^8)</td><td>65536 (2^14)</td></tr>
+        *   <tr><td>2007</td><td>BIFF12</td><td>"XFD" (2^14)</td><td>1048576 (2^20)</td></tr>
+        * </table></blockquote>
+        * POI currently targets BIFF8 (Excel 97-2003), so the following behaviour can be observed for
+        * this method:
+        * <blockquote><table border="0" cellpadding="1" cellspacing="0" 
+        *                 summary="Notable cases.">
+        *   <tr><th>Input&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</th>
+        *      <th>Result&nbsp;</th></tr>
+        *   <tr><td>"A1", 1</td><td>true</td></tr>
+        *   <tr><td>"a111", 1</td><td>true</td></tr>
+        *   <tr><td>"A65536", 1</td><td>true</td></tr>
+        *   <tr><td>"A65537", 1</td><td>false</td></tr>
+        *   <tr><td>"iv1", 2</td><td>true</td></tr>
+        *   <tr><td>"IW1", 2</td><td>false</td></tr>
+        *   <tr><td>"AAA1", 3</td><td>false</td></tr>
+        *   <tr><td>"a111", 1</td><td>true</td></tr>
+        *   <tr><td>"Sheet1", 6</td><td>false</td></tr>
+        * </table></blockquote>
+        */
+       /* package */ static boolean cellReferenceIsWithinRange(String rawSheetName, int numberOfLetters) {
+               
+               if(numberOfLetters > BIFF8_LAST_COLUMN_TEXT_LEN) {
+                       // "Sheet1" case etc
+                       return false; // that was easy
+               }
+               int nDigits = rawSheetName.length() - numberOfLetters;
+               if(nDigits > BIFF8_LAST_ROW_TEXT_LEN) {
+                       return false; 
+               }
+               if(numberOfLetters == BIFF8_LAST_COLUMN_TEXT_LEN) {
+                       String colStr = rawSheetName.substring(0, BIFF8_LAST_COLUMN_TEXT_LEN).toUpperCase();
+                       if(colStr.compareTo(BIFF8_LAST_COLUMN) > 0) {
+                               return false;
+                       }
+               } else {
+                       // apparent column name has less chars than max
+                       // no need to check range
+               }
+               
+               if(nDigits == BIFF8_LAST_ROW_TEXT_LEN) {
+                       String colStr = rawSheetName.substring(numberOfLetters);
+                       // ASCII comparison is valid if digit count is same
+                       if(colStr.compareTo(BIFF8_LAST_ROW) > 0) {
+                               return false;
+                       }
+               } else {
+                       // apparent row has less chars than max
+                       // no need to check range
+               }
+               
+               return true;
+       }
+
+       /**
+        * Note - this method assumes the specified rawSheetName has only letters and digits.  It 
+        * cannot be used to match absolute or range references (using the dollar or colon char).
+        * <p/>
+        * Some notable cases:
+        *    <blockquote><table border="0" cellpadding="1" cellspacing="0" 
+        *                 summary="Notable cases.">
+        *      <tr><th>Input&nbsp;</th><th>Result&nbsp;</th><th>Comments</th></tr>
+        *      <tr><td>"A1"&nbsp;&nbsp;</td><td>true</td><td>&nbsp;</td></tr>
+        *      <tr><td>"a111"&nbsp;&nbsp;</td><td>true</td><td>&nbsp;</td></tr>
+        *      <tr><td>"AA"&nbsp;&nbsp;</td><td>false</td><td>&nbsp;</td></tr>
+        *      <tr><td>"aa1"&nbsp;&nbsp;</td><td>true</td><td>&nbsp;</td></tr>
+        *      <tr><td>"A1A"&nbsp;&nbsp;</td><td>false</td><td>&nbsp;</td></tr>
+        *      <tr><td>"A1A1"&nbsp;&nbsp;</td><td>false</td><td>&nbsp;</td></tr>
+        *      <tr><td>"A$1:$C$20"&nbsp;&nbsp;</td><td>false</td><td>Not a plain cell reference</td></tr>
+        *      <tr><td>"SALES20080101"&nbsp;&nbsp;</td><td>true</td>
+        *                      <td>Still needs delimiting even though well out of range</td></tr>
+        *    </table></blockquote>
+        *  
+        * @return <code>true</code> if there is any possible ambiguity that the specified rawSheetName
+        * could be interpreted as a valid cell name.
+        */
+       /* package */ static boolean nameLooksLikePlainCellReference(String rawSheetName) {
+               Matcher matcher = CELL_REF_PATTERN.matcher(rawSheetName);
+               if(!matcher.matches()) {
+                       return false;
+               }
+               
+               // rawSheetName == "Sheet1" gets this far.
+               String lettersPrefix = matcher.group(1);
+               return cellReferenceIsWithinRange(rawSheetName, lettersPrefix.length());
+       }
+
+}
index 9cc515832522085335637df7551aa657306a9056..1badf519705b489fe8360cf219fd7c57fa2a620a 100644 (file)
@@ -28,7 +28,7 @@ import org.apache.poi.hssf.record.RecordInputStream;
 public class UnknownPtg
     extends Ptg
 {
-    private short size;
+    private short size = 1;
 
     /** Creates new UnknownPtg */
 
index 33417dad7f28e8171f3ff16f497e1e846cd928cb..19ead33f0eb6fdd230a6f0a05f4b22ce0f885111 100644 (file)
@@ -34,20 +34,7 @@ import java.util.Iterator;
 import org.apache.poi.hssf.model.FormulaParser;
 import org.apache.poi.hssf.model.Sheet;
 import org.apache.poi.hssf.model.Workbook;
-import org.apache.poi.hssf.record.BlankRecord;
-import org.apache.poi.hssf.record.BoolErrRecord;
-import org.apache.poi.hssf.record.CellValueRecordInterface;
-import org.apache.poi.hssf.record.CommonObjectDataSubRecord;
-import org.apache.poi.hssf.record.ExtendedFormatRecord;
-import org.apache.poi.hssf.record.FormulaRecord;
-import org.apache.poi.hssf.record.LabelSSTRecord;
-import org.apache.poi.hssf.record.NoteRecord;
-import org.apache.poi.hssf.record.NumberRecord;
-import org.apache.poi.hssf.record.ObjRecord;
-import org.apache.poi.hssf.record.Record;
-import org.apache.poi.hssf.record.SubRecord;
-import org.apache.poi.hssf.record.TextObjectRecord;
-import org.apache.poi.hssf.record.UnicodeString;
+import org.apache.poi.hssf.record.*;
 import org.apache.poi.hssf.record.aggregates.FormulaRecordAggregate;
 import org.apache.poi.hssf.record.formula.Ptg;
 import org.apache.poi.ss.usermodel.Cell;
@@ -1073,4 +1060,31 @@ public class HSSFCell implements Cell
         }
         return comment;
    }
+
+    /**
+     * Returns hyperlink associated with this cell
+     *
+     * @return hyperlink associated with this cell or null if not found
+     */
+    public HSSFHyperlink getHyperlink(){
+        for (Iterator it = sheet.getRecords().iterator(); it.hasNext(); ) {
+            Record rec = ( Record ) it.next();
+            if (rec instanceof HyperlinkRecord){
+                HyperlinkRecord link = (HyperlinkRecord)rec;
+                if(link.getColumn() == record.getColumn() && link.getRow() == record.getRow()){
+                    return new HSSFHyperlink(link);
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Assign a hypelrink to this cell
+     *
+     * @param link hypelrink associated with this cell
+     */
+    public void setHyperlink(HSSFHyperlink link){
+
+    }
 }
index 547ec83038afd68b8151284222f3adc9f0816419..fb7ac49f3954ab212266a36761b196df740cf30e 100644 (file)
@@ -206,12 +206,12 @@ public class HSSFDataFormat implements DataFormat
     }
 
     /**
-     * get the format index that matches the given format string.
-     * Creates a new format if one is not found.  Aliases text to the proper format.
+     * Get the format index that matches the given format
+     *  string, creating a new format entry if required.
+     * Aliases text to the proper format as required.
      * @param format string matching a built in format
      * @return index of format.
      */
-
     public short getFormat( String format )
     {
         ListIterator i;
diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFHyperlink.java b/src/java/org/apache/poi/hssf/usermodel/HSSFHyperlink.java
new file mode 100755 (executable)
index 0000000..e1bd28a
--- /dev/null
@@ -0,0 +1,128 @@
+/* ====================================================================\r
+   Licensed to the Apache Software Foundation (ASF) under one or more\r
+   contributor license agreements.  See the NOTICE file distributed with\r
+   this work for additional information regarding copyright ownership.\r
+   The ASF licenses this file to You under the Apache License, Version 2.0\r
+   (the "License"); you may not use this file except in compliance with\r
+   the License.  You may obtain a copy of the License at\r
+\r
+       http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+   Unless required by applicable law or agreed to in writing, software\r
+   distributed under the License is distributed on an "AS IS" BASIS,\r
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+   See the License for the specific language governing permissions and\r
+   limitations under the License.\r
+==================================================================== */\r
+package org.apache.poi.hssf.usermodel;\r
+\r
+import org.apache.poi.hssf.record.EscherAggregate;\r
+import org.apache.poi.hssf.record.NoteRecord;\r
+import org.apache.poi.hssf.record.TextObjectRecord;\r
+import org.apache.poi.hssf.record.HyperlinkRecord;\r
+import org.apache.poi.ddf.*;\r
+\r
+import java.util.Map;\r
+import java.util.List;\r
+import java.util.Iterator;\r
+\r
+/**\r
+ * Represents a hyperlink.\r
+ *\r
+ * @author Yegor Kozlov\r
+ */\r
+public class HSSFHyperlink {\r
+\r
+    /**\r
+     * Link to a existing file or web page\r
+     */\r
+    public static final int LINK_URL = 1;\r
+\r
+    /**\r
+     * Link to a place in this document\r
+     */\r
+    public static final int LINK_DOCUMENT = 2;\r
+\r
+    /**\r
+     * Link to an E-mail address\r
+     */\r
+    public static final int LINK_EMAIL = 3;\r
+\r
+    /**\r
+     * Unknown type\r
+     */\r
+    public static final int LINK_UNKNOWN = 4;\r
+\r
+    /**\r
+     * Low-level record object that stores the actual hyperlink data\r
+     */\r
+    private HyperlinkRecord record = null;\r
+\r
+    protected HSSFHyperlink( HyperlinkRecord record )\r
+    {\r
+        this.record = record;\r
+    }\r
+\r
+    /**\r
+     * Return the row of the cell that contains the hyperlink\r
+     *\r
+     * @return the 0-based row of the cell that contains the hyperlink\r
+     */\r
+    public int getRow(){\r
+        return record.getRow();\r
+    }\r
+\r
+    /**\r
+     * Set the row of the cell that contains the hyperlink\r
+     *\r
+     * @param row the 0-based row of the cell that contains the hyperlink\r
+     */\r
+    public void setRow(int row){\r
+        record.setRow(row);\r
+    }\r
+\r
+    /**\r
+     * Return the column of the cell that contains the hyperlink\r
+     *\r
+     * @return the 0-based column of the cell that contains the hyperlink\r
+     */\r
+    public short getColumn(){\r
+        return record.getColumn();\r
+    }\r
+\r
+    /**\r
+     * Set the column of the cell that contains the hyperlink\r
+     *\r
+     * @param col the 0-based column of the cell that contains the hyperlink\r
+     */\r
+    public void setColumn(short col){\r
+        record.setColumn(col);\r
+    }\r
+\r
+    /**\r
+     * Hypelink address. Depending on the hyperlink type it can be URL, e-mail, etc.\r
+     *\r
+     * @return  the address of this hyperlink\r
+     */\r
+    public String getAddress(){\r
+        return record.getUrlString();\r
+    }\r
+\r
+    /**\r
+     * Return text to display for this hyperlink\r
+     *\r
+     * @return  text to display\r
+     */\r
+    public String getLabel(){\r
+        return record.getLabel();\r
+    }\r
+\r
+    /**\r
+     * Return the type of this hyperlink\r
+     *\r
+     * @return the type of this hyperlink\r
+     */\r
+    public int getType(){\r
+        throw new RuntimeException("Not implemented");\r
+    }\r
+}\r
index 4265a88af893597636e6e3601bfb448aaf1af8bf..16a48f28f6e6cbb3ae519de731e3d41c52c4b720 100644 (file)
@@ -22,8 +22,6 @@ package org.apache.poi.util;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
-import java.util.*;
-
 /**
  * A logger class that strives to make it as easy as possible for
  * developers to write log calls, while simultaneously making those
@@ -53,7 +51,6 @@ public class CommonsLogger extends POILogger
      * @param level One of DEBUG, INFO, WARN, ERROR, FATAL
      * @param obj1 The object to log.
      */
-
     public void log(final int level, final Object obj1)
     {
         if(level==FATAL)
@@ -98,6 +95,78 @@ public class CommonsLogger extends POILogger
             log.trace(obj1);
           }
         }
+    }
+    
+    /**
+     * Log a message
+     *
+     * @param level One of DEBUG, INFO, WARN, ERROR, FATAL
+     * @param obj1 The object to log.  This is converted to a string.
+     * @param exception An exception to be logged
+     */
+    public void log(final int level, final Object obj1,
+                    final Throwable exception) 
+    {
+        if(level==FATAL)
+        {
+          if(log.isFatalEnabled())
+          {
+            if(obj1 != null)
+               log.fatal(obj1, exception);
+            else
+               log.fatal(exception);
+          }
+        }
+        else if(level==ERROR)
+        {
+          if(log.isErrorEnabled())
+          {
+            if(obj1 != null)
+               log.error(obj1, exception);
+            else
+               log.error(exception);
+          }
+        }
+        else if(level==WARN)
+        {
+          if(log.isWarnEnabled())
+          {
+            if(obj1 != null)
+               log.warn(obj1, exception);
+            else
+               log.warn(exception);
+          }
+        }
+        else if(level==INFO)
+        {
+          if(log.isInfoEnabled())
+          {
+               if(obj1 != null)
+               log.info(obj1, exception);
+               else
+                  log.info(exception);
+          }
+        }
+        else if(level==DEBUG)
+        {
+          if(log.isDebugEnabled())
+          {
+               if(obj1 != null)
+               log.debug(obj1, exception);
+               else
+                  log.debug(exception);
+          }
+        }
+        else
+        {
+          if(log.isTraceEnabled())
+          {
+               if(obj1 != null)
+               log.trace(obj1, exception);
+               else
+                  log.trace(exception);
+          }
+        }
 
     }
 
index b2d358d4d8eaf0a6ee738e30d112366bd0b5c884..514edf90ca9e8cfac5795b7edf3578036136eb11 100644 (file)
@@ -51,7 +51,24 @@ public abstract class POILogger
     
     abstract public void initialize(final String cat);
     
+    /**
+     * Log a message
+     *
+     * @param level One of DEBUG, INFO, WARN, ERROR, FATAL
+     * @param obj1 The object to log.  This is converted to a string.
+     */
     abstract public void log(final int level, final Object obj1);
+    
+    /**
+     * Log a message
+     *
+     * @param level One of DEBUG, INFO, WARN, ERROR, FATAL
+     * @param obj1 The object to log.  This is converted to a string.
+     * @param exception An exception to be logged
+     */
+    abstract public void log(final int level, final Object obj1,
+                    final Throwable exception);
+
 
     /**
      * Check if a logger is enabled to log at the specified level
@@ -237,17 +254,15 @@ public abstract class POILogger
     }
 
     /**
-     * Log a message
+     * Log an exception, without a message
      *
      * @param level One of DEBUG, INFO, WARN, ERROR, FATAL
-     * @param obj1 The object to log.  This is converted to a string.
      * @param exception An exception to be logged
      */
 
-    public void log(final int level, final Object obj1,
-                    final Throwable exception)
+    public void log(final int level, final Throwable exception)
     {
-        log(level , obj1, exception);
+        log(level, null, exception);
     }
 
     /**
index 8b3dc50d9a715d612162e7615f98f18c79fdafbc..af678e186ac92e8c36059deac3a83c36696a90e4 100644 (file)
@@ -49,8 +49,24 @@ public class SystemOutLogger extends POILogger
 
     public void log(final int level, final Object obj1)
     {
-        if (check(level))
+       log(level, obj1, null);
+    }
+    
+    /**
+     * Log a message
+     *
+     * @param level One of DEBUG, INFO, WARN, ERROR, FATAL
+     * @param obj1 The object to log.  This is converted to a string.
+     * @param exception An exception to be logged
+     */
+    public void log(final int level, final Object obj1,
+                    final Throwable exception) {
+        if (check(level)) {
             System.out.println("["+cat+"] "+obj1);
+            if(exception != null) {
+               exception.printStackTrace(System.out);
+            }
+        }
     }
 
     /**
index 7eae4edc4c3c56fab39af76d0a575bcfc8730f48..f9cc43a7eac6f98f779ce0edbb909b0261b3e6be 100644 (file)
@@ -23,6 +23,8 @@ import org.apache.poi.hslf.record.*;
 import org.apache.poi.hslf.usermodel.PictureData;
 import org.apache.poi.hslf.usermodel.SlideShow;
 import org.apache.poi.hslf.exceptions.HSLFException;
+import org.apache.poi.util.POILogger;
+import org.apache.poi.util.POILogFactory;
 
 import java.awt.*;
 import java.util.*;
@@ -33,6 +35,9 @@ import java.util.*;
  * @author Yegor Kozlov
  */
 public class Fill {
+    // For logging
+    protected POILogger logger = POILogFactory.getLogger(this.getClass());
+
     /**
      *  Fill with a solid color
      */
@@ -208,15 +213,18 @@ public class Fill {
 
         java.util.List lst = bstore.getChildRecords();
         int idx = p.getPropertyValue();
-        EscherBSERecord bse = (EscherBSERecord)lst.get(idx);
-        for ( int i = 0; i < pict.length; i++ ) {
-                       if (pict[i].getOffset() ==  bse.getOffset()){
-                return pict[i];
+        if (idx == 0){
+            logger.log(POILogger.ERROR, "no reference to picture data found ");
+        } else {
+            EscherBSERecord bse = (EscherBSERecord)lst.get(idx - 1);
+            for ( int i = 0; i < pict.length; i++ ) {
+                if (pict[i].getOffset() ==  bse.getOffset()){
+                    return pict[i];
+                }
             }
         }
-        throw new HSLFException("Picture data not found: \n" +
-                "  bse: " + bse + " at " + bse.getOffset() );
 
+        return null;
     }
 
     /**
index 0740e23bce0002f07319e90854495ac1f63eca95..90efd5f3ee5a1f41348ede304f5097b43861fc97 100644 (file)
@@ -109,7 +109,7 @@ public class Picture extends SimpleShape {
      */
     public int getPictureIndex(){
         EscherOptRecord opt = (EscherOptRecord)getEscherChild(_escherContainer, EscherOptRecord.RECORD_ID);
-        EscherSimpleProperty prop = (EscherSimpleProperty)getEscherProperty(opt, EscherProperties.BLIP__BLIPTODISPLAY + 0x4000);
+        EscherSimpleProperty prop = (EscherSimpleProperty)getEscherProperty(opt, EscherProperties.BLIP__BLIPTODISPLAY);
         return prop == null ? 0 : prop.getPropertyValue();
     }
 
index 56d77764e9256d21648bfc839f1b7884b3c0407b..5cff81a8d7234fc05c821b6a148730f3acb265eb 100644 (file)
@@ -227,7 +227,7 @@ public abstract class Shape {
         for ( Iterator iterator = opt.getEscherProperties().iterator(); iterator.hasNext(); )
         {
             EscherProperty prop = (EscherProperty) iterator.next();
-            if (prop.getId() == propId)
+            if (prop.getPropertyNumber() == propId)
                 return prop;
         }
         return null;
index 201a069fc4797210ed9e378276b5935cdc78ecbe..ea7201751d79310cb73d43925f549a8779c06e02 100644 (file)
@@ -262,4 +262,11 @@ public class Slide extends Sheet
         SlideAtom sa = getSlideRecord().getSlideAtom();
         return sa.getFollowMasterBackground();
     }
+
+    public Background getBackground() {
+        if(getFollowMasterBackground())
+            return getMasterSheet().getBackground();
+        else
+            return super.getBackground();
+    }
 }
index e25fad66e40ad0bac0f29dadf781aa7bd3bd458c..b75864e72d80a610166863421f1b3a69d0c3e1c2 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 Columns 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.NumberEval;
+import org.apache.poi.hssf.record.formula.eval.RefEval;
+
+/**
+ * Implementation for Excel COLUMNS function.
+ * 
+ * @author Josh Micich
+ */
+public final class Columns implements Function {
 
+       public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
+               switch(args.length) {
+                       case 1:
+                               // expected
+                               break;
+                       case 0:
+                               // too few arguments
+                               return ErrorEval.VALUE_INVALID;
+                       default:
+                               // too many arguments
+                               return ErrorEval.VALUE_INVALID;
+               }
+               Eval firstArg = args[0];
+               
+               int result;
+        if (firstArg instanceof AreaEval) {
+            AreaEval ae = (AreaEval) firstArg;
+            result = ae.getLastColumn() - ae.getFirstColumn() + 1;
+        } else if (firstArg instanceof RefEval) {
+            result = 1;
+        } else { // anything else is not valid argument
+            return ErrorEval.VALUE_INVALID;
+        }
+        return new NumberEval(result);
+       }
 }
index 6c6b3057425810ae0303cc9e5cca027d58b8ffad..9061e77e5da9cc5b13044bbb3986f666817846dc 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.RefEval;
+import org.apache.poi.hssf.record.formula.eval.StringEval;
+import org.apache.poi.hssf.record.formula.eval.ValueEval;
+
+/**
+ * Counts the number of cells that contain data within the list of arguments. 
  *
+ * Excel Syntax
+ * COUNTA(value1,value2,...)
+ * Value1, value2, ...   are 1 to 30 arguments representing the values or ranges to be counted.
+ * 
+ * @author Josh Micich
  */
-package org.apache.poi.hssf.record.formula.functions;
+public final class Counta implements Function {
+
+       public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
+               int nArgs = args.length;
+               if (nArgs < 1) {
+                       // too few arguments
+                       return ErrorEval.VALUE_INVALID;
+               }
+
+               if (nArgs > 30) {
+                       // too many arguments
+                       return ErrorEval.VALUE_INVALID;
+               }
+               
+               int temp = 0;
+               // Note - observed behavior of Excel:
+               // Error values like #VALUE!, #REF!, #DIV/0!, #NAME? etc don't cause this COUNTA to return an error
+               // in fact, they seem to get counted
+               
+               for(int i=0; i<nArgs; i++) {
+                       temp += countArg(args[i]);
+                       
+               }
+               return new NumberEval(temp);
+       }
+
+       private static int countArg(Eval eval) {
+        if (eval instanceof AreaEval) {
+            AreaEval ae = (AreaEval) eval;
+            return countAreaEval(ae);
+        }
+        if (eval instanceof RefEval) {
+            RefEval refEval = (RefEval)eval;
+                       return countValue(refEval.getInnerValueEval());
+        }
+        if (eval instanceof NumberEval) {
+            return 1;
+        }
+        if (eval instanceof StringEval) {
+            return 1;
+        }
+        
+               
+               throw new RuntimeException("Unexpected eval type (" + eval.getClass().getName() + ")");
+       }
+
+       private static int countAreaEval(AreaEval ae) {
+               
+               int temp = 0;
+               ValueEval[] values = ae.getValues();
+               for (int i = 0; i < values.length; i++) {
+                       ValueEval val = values[i];
+                       if(val == null) {
+                               // seems to occur.  Really we would have expected BlankEval
+                               continue;
+                       }
+                       temp += countValue(val);
+                       
+               }
+               return temp;
+       }
+
+       private static int countValue(ValueEval valueEval) {
+               
+               if(valueEval == BlankEval.INSTANCE) {
+                       return 0;
+               }
+               
+               if(valueEval instanceof BlankEval) {
+                       // wouldn't need this if BlankEval was final
+                       return 0;
+               }
 
-public class Counta extends NotImplementedFunction {
+               if(valueEval instanceof ErrorEval) {
+                       // note - error values are counted
+                       return 1;
+               }
+               // also empty strings and zeros are counted too
 
+               return 1;
+       }
 }
index f97b8a59abca08d020c20ba3aa79c27baffab6b9..2e445a8bf6bb45ef0d27a620db603886fc179696 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 Countif extends NotImplementedFunction {
+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.RefEval;
+import org.apache.poi.hssf.record.formula.eval.StringEval;
+import org.apache.poi.hssf.record.formula.eval.ValueEval;
+
+/**
+ * Implementation for the function COUNTIF<p/>
+ * 
+ * Syntax: COUNTIF ( range, criteria )
+ *    <table border="0" cellpadding="1" cellspacing="0" summary="Parameter descriptions">
+ *      <tr><th>range&nbsp;&nbsp;&nbsp;</th><td>is the range of cells to be counted based on the criteria</td></tr>
+ *      <tr><th>criteria</th><td>is used to determine which cells to count</td></tr>
+ *    </table>
+ * <p/>
+ * 
+ * @author Josh Micich
+ */
+public final class Countif implements Function {
+       
+       /**
+        * Common interface for the matching criteria.
+        */
+       private interface I_MatchPredicate {
+               boolean matches(Eval x);
+       }
+       
+       private static final class NumberMatcher implements I_MatchPredicate {
+
+               private final double _value;
+
+               public NumberMatcher(double value) {
+                       _value = value;
+               }
+
+               public boolean matches(Eval x) {
+                       if(x instanceof StringEval) {
+                               // if the target(x) is a string, but parses as a number
+                               // it may still count as a match
+                               StringEval se = (StringEval)x;
+                               Double val = parseDouble(se.getStringValue());
+                               if(val == null) {
+                                       // x is text that is not a number
+                                       return false;
+                               }
+                               return val.doubleValue() == _value;
+                       }
+                       if(!(x instanceof NumberEval)) {
+                               return false;
+                       }
+                       NumberEval ne = (NumberEval) x;
+                       return ne.getNumberValue() == _value;
+               }
+       }
+       private static final class BooleanMatcher implements I_MatchPredicate {
+
+               private final boolean _value;
+
+               public BooleanMatcher(boolean value) {
+                       _value = value;
+               }
+
+               public boolean matches(Eval x) {
+                       if(x instanceof StringEval) {
+                               StringEval se = (StringEval)x;
+                               Boolean val = parseBoolean(se.getStringValue());
+                               if(val == null) {
+                                       // x is text that is not a boolean
+                                       return false;
+                               }
+                               if (true) { // change to false to observe more intuitive behaviour
+                                       // Note - Unlike with numbers, it seems that COUNTA never matches 
+                                       // boolean values when the target(x) is a string
+                                       return false;
+                               }
+                               return val.booleanValue() == _value;
+                       }
+                       if(!(x instanceof BoolEval)) {
+                               return false;
+                       }
+                       BoolEval be = (BoolEval) x;
+                       return be.getBooleanValue() == _value;
+               }
+       }
+       private static final class StringMatcher implements I_MatchPredicate {
+
+               private final String _value;
+
+               public StringMatcher(String value) {
+                       _value = value;
+               }
+
+               public boolean matches(Eval x) {
+                       if(!(x instanceof StringEval)) {
+                               return false;
+                       }
+                       StringEval se = (StringEval) x;
+                       return se.getStringValue() == _value;
+               }
+       }
+
+       public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
+               switch(args.length) {
+                       case 2:
+                               // expected
+                               break;
+                       default:
+                               // TODO - it doesn't seem to be possible to enter COUNTIF() into Excel with the wrong arg count
+                               // perhaps this should be an exception
+                               return ErrorEval.VALUE_INVALID;
+               }
+               
+               AreaEval range = (AreaEval) args[0];
+               Eval criteriaArg = args[1];
+               if(criteriaArg instanceof RefEval) {
+                       // criteria is not a literal value, but a cell reference
+                       // for example COUNTIF(B2:D4, E1)
+                       RefEval re = (RefEval)criteriaArg;
+                       criteriaArg = re.getInnerValueEval();
+               } else {
+                       // other non literal tokens such as function calls, have been fully evaluated
+                       // for example COUNTIF(B2:D4, COLUMN(E1))
+               }
+               I_MatchPredicate mp = createCriteriaPredicate(criteriaArg);
+               return countMatchingCellsInArea(range, mp);
+       }
+       /**
+        * @return the number of evaluated cells in the range that match the specified criteria
+        */
+       private Eval countMatchingCellsInArea(AreaEval range, I_MatchPredicate criteriaPredicate) {
+               ValueEval[] values = range.getValues();
+               int result = 0;
+               for (int i = 0; i < values.length; i++) {
+                       if(criteriaPredicate.matches(values[i])) {
+                               result++;
+                       }
+               }
+               return new NumberEval(result);
+       }
+       
+       private static I_MatchPredicate createCriteriaPredicate(Eval evaluatedCriteriaArg) {
+               if(evaluatedCriteriaArg instanceof NumberEval) {
+                       return new NumberMatcher(((NumberEval)evaluatedCriteriaArg).getNumberValue());
+               }
+               if(evaluatedCriteriaArg instanceof BoolEval) {
+                       return new BooleanMatcher(((BoolEval)evaluatedCriteriaArg).getBooleanValue());
+               }
+               
+               if(evaluatedCriteriaArg instanceof StringEval) {
+                       return createGeneralMatchPredicate((StringEval)evaluatedCriteriaArg);
+               }
+               throw new RuntimeException("Unexpected type for criteria (" 
+                               + evaluatedCriteriaArg.getClass().getName() + ")");
+       }
+
+       /**
+        * When the second argument is a string, many things are possible
+        */
+       private static I_MatchPredicate createGeneralMatchPredicate(StringEval stringEval) {
+               String value = stringEval.getStringValue();
+               char firstChar = value.charAt(0);
+               Boolean booleanVal = parseBoolean(value);
+               if(booleanVal != null) {
+                       return new BooleanMatcher(booleanVal.booleanValue());
+               }
+               
+               Double doubleVal = parseDouble(value);
+               if(doubleVal != null) {
+                       return new NumberMatcher(doubleVal.doubleValue());
+               }
+               switch(firstChar) {
+                       case '>':
+                       case '<':
+                       case '=':
+                               throw new RuntimeException("Incomplete code - criteria expressions such as '"
+                                               + value + "' not supported yet");
+               }
+               
+               //else - just a plain string with no interpretation.
+               return new StringMatcher(value);
+       }
 
+       /**
+        * Under certain circumstances COUNTA will equate a plain number with a string representation of that number
+        */
+       /* package */ static Double parseDouble(String strRep) {
+               if(!Character.isDigit(strRep.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(strRep);
+               } catch (NumberFormatException e) {
+                       return null;
+               }
+               return new Double(val);
+       }
+       /**
+        * Boolean literals ('TRUE', 'FALSE') treated similarly but NOT same as numbers. 
+        */
+       /* package */ static Boolean parseBoolean(String strRep) {
+               switch(strRep.charAt(0)) {
+                       case 't':
+                       case 'T':
+                               if("TRUE".equalsIgnoreCase(strRep)) {
+                                       return Boolean.TRUE;
+                               }
+                               break;
+                       case 'f':
+                       case 'F':
+                               if("FALSE".equalsIgnoreCase(strRep)) {
+                                       return Boolean.FALSE;
+                               }
+                               break;
+               }
+               return null;
+       }
 }
index db798ee0faefa19c9f2fb3ed977a63816a61b66c..aebf6aab0d4843ab3b0b88f7aa1b2238e163123a 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 Index 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.NumberEval;
+import org.apache.poi.hssf.record.formula.eval.RefEval;
+
+/**
+ * Implementation for the Excel function INDEX<p/>
+ * 
+ * Syntax : <br/>
+ *  INDEX ( reference, row_num[, column_num [, area_num]])</br>
+ *  INDEX ( array, row_num[, column_num])
+ *    <table border="0" cellpadding="1" cellspacing="0" summary="Parameter descriptions">
+ *      <tr><th>reference</th><td>typically an area reference, possibly a union of areas</td></tr>
+ *      <tr><th>array</th><td>a literal array value (currently not supported)</td></tr>
+ *      <tr><th>row_num</th><td>selects the row within the array or area reference</td></tr>
+ *      <tr><th>column_num</th><td>selects column within the array or area reference. default is 1</td></tr>
+ *      <tr><th>area_num</th><td>used when reference is a union of areas</td></tr>
+ *    </table>
+ * <p/>
+ * 
+ * @author Josh Micich
+ */
+public final class Index implements Function {
 
+       // TODO - javadoc for interface method
+       public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
+               int nArgs = args.length;
+               if(nArgs < 2) {
+                       // too few arguments
+                       return ErrorEval.VALUE_INVALID;
+               }
+               Eval firstArg = args[0];
+               if(firstArg instanceof AreaEval) {
+                       AreaEval reference = (AreaEval) firstArg;
+                       
+                       int rowIx = 0;
+                       int columnIx = 0;
+                       int areaIx = 0;
+                       switch(nArgs) {
+                               case 4:
+                                       areaIx = convertIndexArgToZeroBase(args[3]);
+                                       throw new RuntimeException("Incomplete code" +
+                                                       " - don't know how to support the 'area_num' parameter yet)");
+                                       // Excel expression might look like this "INDEX( (A1:B4, C3:D6, D2:E5 ), 1, 2, 3)
+                                       // In this example, the 3rd area would be used i.e. D2:E5, and the overall result would be E2
+                                       // Token array might be encoded like this: MemAreaPtg, AreaPtg, AreaPtg, UnionPtg, UnionPtg, ParenthesesPtg
+                                       // The formula parser doesn't seem to support this yet. Not sure if the evaluator does either
+                                       
+                               case 3:
+                                       columnIx = convertIndexArgToZeroBase(args[2]);
+                               case 2:
+                                       rowIx = convertIndexArgToZeroBase(args[1]);
+                                       break;
+                               default:
+                                       // too many arguments
+                                       return ErrorEval.VALUE_INVALID;
+                       }
+                       
+               int nColumns = reference.getLastColumn()-reference.getFirstColumn()+1;
+                       int index = rowIx * nColumns + columnIx;
+                       
+                       return reference.getValues()[index];
+               }
+               
+               // else the other variation of this function takes an array as the first argument
+               // it seems like interface 'ArrayEval' does not even exist yet
+               
+               throw new RuntimeException("Incomplete code - cannot handle first arg of type ("
+                               + firstArg.getClass().getName() + ")");
+       }
+       
+       /**
+        * takes a NumberEval representing a 1-based index and returns the zero-based int value
+        */
+       private static int convertIndexArgToZeroBase(Eval ev) {
+               NumberEval ne;
+               if(ev instanceof RefEval) {
+                       // TODO - write junit to justify this
+                       RefEval re = (RefEval) ev;
+                       ne = (NumberEval) re.getInnerValueEval();
+               } else {
+                       ne = (NumberEval)ev;
+               }
+               
+               return (int)ne.getNumberValue() - 1;
+       }
 }
index 47e4dc8702d9bc824054fc707450c5b56f7828aa..6a4eb8edb7b45f46149c1c805e38a3641dc01bdc 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 Rows 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.NumberEval;
+import org.apache.poi.hssf.record.formula.eval.RefEval;
+
+/**
+ * Implementation for Excel COLUMNS function.
+ * 
+ * @author Josh Micich
+ */
+public final class Rows implements Function {
 
+       public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
+               switch(args.length) {
+                       case 1:
+                               // expected
+                               break;
+                       case 0:
+                               // too few arguments
+                               return ErrorEval.VALUE_INVALID;
+                       default:
+                               // too many arguments
+                               return ErrorEval.VALUE_INVALID;
+               }
+               Eval firstArg = args[0];
+               
+               int result;
+        if (firstArg instanceof AreaEval) {
+            AreaEval ae = (AreaEval) firstArg;
+            result = ae.getLastRow() - ae.getFirstRow() + 1;
+        } else if (firstArg instanceof RefEval) {
+            result = 1;
+        } else { // anything else is not valid argument
+            return ErrorEval.VALUE_INVALID;
+        }
+        return new NumberEval(result);
+       }
 }
index ff9cf2b9c834cc7960ccccca63ac79fc8ad44d9b..a88e32360ee433114c6a2ad79c357102f22edba8 100644 (file)
@@ -58,7 +58,7 @@ public class TableRow
         p = getParagraph(end);
         s = p.text();
       }
-      _cells[cellIndex] = new TableCell(start, end, this, levelNum,
+      _cells[cellIndex] = new TableCell(start, end+1, this, levelNum,
                                         _tprops.getRgtc()[cellIndex],
                                         _tprops.getRgdxaCenter()[cellIndex],
                                         _tprops.getRgdxaCenter()[cellIndex+1]-_tprops.getRgdxaCenter()[cellIndex]);
diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/data/44296.ppt b/src/scratchpad/testcases/org/apache/poi/hslf/data/44296.ppt
new file mode 100755 (executable)
index 0000000..1e0529d
Binary files /dev/null and b/src/scratchpad/testcases/org/apache/poi/hslf/data/44296.ppt differ
index 996a733ac981a5ea444cb099a0416bbad5ea9f98..f3f5f8e7ee8a653fca73bb309df4456c51668e72 100644 (file)
@@ -330,4 +330,24 @@ public class TestBugs extends TestCase {
             assertEquals(tr1[i].getText(), tr2[i].getText());\r
         }\r
     }\r
+\r
+    /**\r
+     * Bug 44296: HSLF Not Extracting Slide Background Image\r
+     */\r
+    public void test44296  () throws Exception {\r
+        FileInputStream is = new FileInputStream(new File(cwd, "44296.ppt"));\r
+        SlideShow ppt = new SlideShow(is);\r
+        is.close();\r
+\r
+        Slide slide = ppt.getSlides()[0];\r
+\r
+        Background b = slide.getBackground();\r
+        Fill f = b.getFill();\r
+        assertEquals(Fill.FILL_PICTURE, f.getFillType());\r
+\r
+        PictureData pict = f.getPictureData();\r
+        assertNotNull(pict);\r
+        assertEquals(Picture.JPEG, pict.getType());\r
+    }\r
+\r
 }\r
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/data/44297.xls b/src/scratchpad/testcases/org/apache/poi/hssf/data/44297.xls
new file mode 100755 (executable)
index 0000000..bc65efd
Binary files /dev/null and b/src/scratchpad/testcases/org/apache/poi/hssf/data/44297.xls differ
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java
new file mode 100755 (executable)
index 0000000..b5e0843
--- /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.hssf.record.formula.functions;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Direct tests for all implementors of <code>Function</code>.
+ * 
+ * @author Josh Micich
+ */
+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(TestCountFuncs.class);
+               result.addTestSuite(TestDate.class);
+               result.addTestSuite(TestFinanceLib.class);
+               result.addTestSuite(TestIndex.class);
+               result.addTestSuite(TestMathX.class);
+               result.addTestSuite(TestRowCol.class);
+               result.addTestSuite(TestStatsLib.class);
+               return result;
+       }
+
+}
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/EvalFactory.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/EvalFactory.java
new file mode 100755 (executable)
index 0000000..958c486
--- /dev/null
@@ -0,0 +1,63 @@
+/* ====================================================================
+   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.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.ValueEval;
+
+/**
+ * Test helper class for creating mock <code>Eval</code> objects
+ * 
+ * @author Josh Micich
+ */
+final class EvalFactory {
+       private static final NumberEval ZERO = new NumberEval(0);
+
+       private EvalFactory() {
+               // no instances of this class
+       }
+
+       /**
+        * Creates a dummy AreaEval (filled with zeros)
+        * <p/>
+        * nCols and nRows could have been derived
+        */
+       public static AreaEval createAreaEval(String areaRefStr, int nCols, int nRows) {
+               int nValues = nCols * nRows;
+               ValueEval[] values = new ValueEval[nValues];
+               for (int i = 0; i < nValues; i++) {
+                       values[i] = ZERO;
+               }
+               
+               return new Area2DEval(new AreaPtg(areaRefStr), values);
+       }
+
+       /**
+        * Creates a single RefEval (with value zero)
+        */
+       public static RefEval createRefEval(String refStr) {
+               return new Ref2DEval(new ReferencePtg(refStr), ZERO, true);
+       }
+}
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/NumericFunctionInvoker.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/NumericFunctionInvoker.java
new file mode 100755 (executable)
index 0000000..87405a4
--- /dev/null
@@ -0,0 +1,101 @@
+/* ====================================================================
+   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.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;
+
+/**
+ * Test helper class for invoking functions with numeric results.
+ * 
+ * @author Josh Micich
+ */
+final class NumericFunctionInvoker {
+
+       private NumericFunctionInvoker() {
+               // no instances of this class
+       }
+       
+       private static final class NumericEvalEx extends Exception {
+               public NumericEvalEx(String msg) {
+                       super(msg);
+               }
+       }
+       
+       /**
+        * Invokes the specified function with the arguments.
+        * <p/>
+        * Assumes that the cell coordinate parameters of
+        *  <code>Function.evaluate(args, srcCellRow, srcCellCol)</code>
+        * are not required.
+        * <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(Function f, Eval[] args) {
+               try {
+                       return invokeInternal(f, args, -1, -1);
+               } 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)
+                               throws NumericEvalEx {
+               Eval evalResult = f.evaluate(args, srcCellRow, (short)srcCellCol);
+               if(evalResult == null) {
+                       throw new NumericEvalEx("Result object was null");
+               }
+               if(evalResult instanceof ErrorEval) {
+                       ErrorEval ee = (ErrorEval) evalResult;
+                       throw new NumericEvalEx(formatErrorMessage(ee));
+               }
+               if(!(evalResult instanceof NumericValueEval)) {
+                       throw new NumericEvalEx("Result object type (" + evalResult.getClass().getName()
+                                       + ") is invalid.  Expected implementor of (" 
+                                       + NumericValueEval.class.getName() + ")");
+               }
+               
+               NumericValueEval result = (NumericValueEval) evalResult;
+               return result.getNumberValue();
+       }
+       private static String formatErrorMessage(ErrorEval ee) {
+               if(errorCodesAreEqual(ee, ErrorEval.FUNCTION_NOT_IMPLEMENTED)) {
+                       return "Function not implemented";
+               }
+               if(errorCodesAreEqual(ee, ErrorEval.UNKNOWN_ERROR)) {
+                       return "Unknown error";
+               }
+               return "Error code=" + ee.getErrorCode();
+       }
+       private static boolean errorCodesAreEqual(ErrorEval a, ErrorEval b) {
+               if(a==b) {
+                       return true;
+               }
+               return a.getErrorCode() == b.getErrorCode();
+       }
+
+}
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestCountFuncs.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestCountFuncs.java
new file mode 100755 (executable)
index 0000000..fbaace9
--- /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.record.formula.functions;
+
+import junit.framework.TestCase;
+
+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.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;
+
+/**
+ * Test cases for COUNT(), COUNTA() COUNTIF(), COUNTBLANK()
+ * 
+ * @author Josh Micich
+ */
+public final class TestCountFuncs extends TestCase {
+
+       public TestCountFuncs(String testName) {
+               super(testName);
+       }
+       
+       public void testCountA() {
+               
+               Eval[] args;
+               
+               args = new Eval[] {
+                       new NumberEval(0),       
+               };
+               confirmCountA(1, args);
+               
+               args = new Eval[] {
+                       new NumberEval(0),      
+                       new NumberEval(0),
+                       new StringEval(""),
+               };
+               confirmCountA(3, args);
+               
+               args = new Eval[] {
+                       EvalFactory.createAreaEval("D2:F5", 3, 4),      
+               };
+               confirmCountA(12, args);
+               
+               args = new Eval[] {
+                       EvalFactory.createAreaEval("D1:F5", 3, 5),      // 15
+                       EvalFactory.createRefEval("A1"),        
+                       EvalFactory.createAreaEval("A1:F6", 7, 6),      // 42
+                       new NumberEval(0),
+               };
+               confirmCountA(59, args);
+       }
+
+       public void testCountIf() {
+               
+               AreaEval range;
+               ValueEval[] values;
+               
+               // when criteria is a boolean value
+               values = new ValueEval[] {
+                               new NumberEval(0),      
+                               new StringEval("TRUE"), // note - does not match boolean TRUE
+                               BoolEval.TRUE,
+                               BoolEval.FALSE,
+                               BoolEval.TRUE,
+                               BlankEval.INSTANCE,
+               };
+               range = createAreaEval("A1:B2", values);
+               confirmCountIf(2, range, BoolEval.TRUE);
+               
+               // when criteria is numeric
+               values = new ValueEval[] {
+                               new NumberEval(0),      
+                               new StringEval("2"),    
+                               new StringEval("2.001"),        
+                               new NumberEval(2),      
+                               new NumberEval(2),      
+                               BoolEval.TRUE,
+                               BlankEval.INSTANCE,
+               };
+               range = createAreaEval("A1:B2", values);
+               confirmCountIf(3, range, new NumberEval(2));
+               // note - same results when criteria is a string that parses as the number with the same value
+               confirmCountIf(3, range, new StringEval("2.00"));
+               
+               if (false) { // not supported yet: 
+                       // when criteria is an expression (starting with a comparison operator)
+                       confirmCountIf(4, range, new StringEval(">1"));
+               }
+       }
+       /**
+        * special case where the criteria argument is a cell reference
+        */
+       public void testCountIfWithCriteriaReference() {
+
+               ValueEval[] values = { 
+                               new NumberEval(22),
+                               new NumberEval(25),
+                               new NumberEval(21),
+                               new NumberEval(25),
+                               new NumberEval(25),
+                               new NumberEval(25),
+               };
+               Area2DEval arg0 = new Area2DEval(new AreaPtg("C1:C6"), values);
+               
+               Ref2DEval criteriaArg = new Ref2DEval(new ReferencePtg("A1"), new NumberEval(25), true);
+               Eval[] args=  { arg0, criteriaArg, };
+               
+               double actual = NumericFunctionInvoker.invoke(new Countif(), args);
+               assertEquals(4, actual, 0D);
+       }
+       
+
+       private static AreaEval createAreaEval(String areaRefStr, ValueEval[] values) {
+               return new Area2DEval(new AreaPtg(areaRefStr), values);
+       }
+
+       private static void confirmCountA(int expected, Eval[] args) {
+               double result = NumericFunctionInvoker.invoke(new Counta(), args);
+               assertEquals(expected, result, 0);
+       }
+       private static void confirmCountIf(int expected, AreaEval range, Eval criteria) {
+               
+               Eval[] args = { range, criteria, };
+               double result = NumericFunctionInvoker.invoke(new Countif(), args);
+               assertEquals(expected, result, 0);
+       }
+}
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestIndex.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestIndex.java
new file mode 100755 (executable)
index 0000000..902c412
--- /dev/null
@@ -0,0 +1,89 @@
+/* ====================================================================
+   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.Eval;
+import org.apache.poi.hssf.record.formula.eval.NumberEval;
+import org.apache.poi.hssf.record.formula.eval.ValueEval;
+
+/**
+ * Tests for the INDEX() function
+ * 
+ * @author Josh Micich
+ */
+public final class TestIndex extends TestCase {
+
+       public TestIndex(String testName) {
+               super(testName);
+       }
+       
+       private static final double[] TEST_VALUES0 = {
+                       1, 2,
+                       3, 4,
+                       5, 6,
+                       7, 8,
+                       9, 10,
+                       11, 12,
+                       13, // excess array element. TODO - Area2DEval currently has no validation to ensure correct size of values array
+       };
+       
+       /**
+        * For the case when the first argument to INDEX() is an area reference
+        */
+       public void testEvaluateAreaReference() {
+               
+               double[] values = TEST_VALUES0;
+               confirmAreaEval("C1:D6", values, 4, 1, 7);
+               confirmAreaEval("C1:D6", values, 6, 2, 12);
+               confirmAreaEval("C1:D6", values, 3, -1, 5);
+               
+               // now treat same data as 3 columns, 4 rows
+               confirmAreaEval("C10:E13", values, 2, 2, 5); 
+               confirmAreaEval("C10:E13", values, 4, -1, 10);
+       }
+       
+       /**
+        * @param areaRefString in Excel notation e.g. 'D2:E97'
+        * @param dValues array of evaluated values for the area reference
+        * @param rowNum 1-based
+        * @param colNum 1-based, pass -1 to signify argument not present
+        */
+       private static void confirmAreaEval(String areaRefString, double[] dValues, 
+                       int rowNum, int colNum, double expectedResult) {
+               ValueEval[] values = new ValueEval[dValues.length];
+               for (int i = 0; i < values.length; i++) {
+                       values[i] = new NumberEval(dValues[i]);
+               }
+               Area2DEval arg0 = new Area2DEval(new AreaPtg(areaRefString), values);
+               
+               Eval[] args;
+               if (colNum > 0) {
+                       args = new Eval[] { arg0, new NumberEval(rowNum), new NumberEval(colNum), };
+               } else {
+                       args = new Eval[] { arg0, new NumberEval(rowNum), };
+               }
+               
+               double actual = NumericFunctionInvoker.invoke(new Index(), args);
+               assertEquals(expectedResult, actual, 0D);
+       }
+}
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestRowCol.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestRowCol.java
new file mode 100755 (executable)
index 0000000..4002c30
--- /dev/null
@@ -0,0 +1,102 @@
+/* ====================================================================
+   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.Eval;
+
+/**
+ * Tests for ROW(), ROWS(), COLUMN(), COLUMNS()
+ * 
+ * @author Josh Micich
+ */
+public final class TestRowCol extends TestCase {
+
+       public TestRowCol(String testName) {
+               super(testName);
+       }
+       
+       public void testCol() {
+               Function target = new Column();
+               {
+                       Eval[] args = { EvalFactory.createRefEval("C5"), };
+                       double actual = NumericFunctionInvoker.invoke(target, args);
+                       assertEquals(3, actual, 0D);
+               }
+               {
+                       Eval[] args = { EvalFactory.createAreaEval("E2:H12", 4, 11), };
+                       double actual = NumericFunctionInvoker.invoke(target, args);
+                       assertEquals(5, actual, 0D);
+               }
+       }
+       
+       public void testRow() {
+               Function target = new Row();
+               {
+                       Eval[] args = { EvalFactory.createRefEval("C5"), };
+                       double actual = NumericFunctionInvoker.invoke(target, args);
+                       assertEquals(5, actual, 0D);
+               }
+               {
+                       Eval[] args = { EvalFactory.createAreaEval("E2:H12", 4, 11), };
+                       double actual = NumericFunctionInvoker.invoke(target, args);
+                       assertEquals(2, actual, 0D);
+               }
+       }
+       
+       public void testColumns() {
+               
+               confirmColumnsFunc("A1:F1", 6, 1);
+               confirmColumnsFunc("A1:C2", 3, 2);
+               confirmColumnsFunc("A1:B3", 2, 3);
+               confirmColumnsFunc("A1:A6", 1, 6);
+               
+               Eval[] args = { EvalFactory.createRefEval("C5"), };
+               double actual = NumericFunctionInvoker.invoke(new Columns(), args);
+               assertEquals(1, actual, 0D);
+       }
+       
+       public void testRows() {
+               
+               confirmRowsFunc("A1:F1", 6, 1);
+               confirmRowsFunc("A1:C2", 3, 2);
+               confirmRowsFunc("A1:B3", 2, 3);
+               confirmRowsFunc("A1:A6", 1, 6);
+               
+               Eval[] args = { EvalFactory.createRefEval("C5"), };
+               double actual = NumericFunctionInvoker.invoke(new Rows(), args);
+               assertEquals(1, actual, 0D);
+       }
+       
+       private static void confirmRowsFunc(String areaRefStr, int nCols, int nRows) {
+               Eval[] args = { EvalFactory.createAreaEval(areaRefStr, nCols, nRows), };
+
+               double actual = NumericFunctionInvoker.invoke(new Rows(), args);
+               assertEquals(nRows, actual, 0D);
+       }
+       
+
+       private static void confirmColumnsFunc(String areaRefStr, int nCols, int nRows) {
+               Eval[] args = { EvalFactory.createAreaEval(areaRefStr, nCols, nRows), };
+
+               double actual = NumericFunctionInvoker.invoke(new Columns(), args);
+               assertEquals(nCols, actual, 0D);
+       }
+}
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestBug44297.java b/src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestBug44297.java
new file mode 100755 (executable)
index 0000000..ce4afd3
--- /dev/null
@@ -0,0 +1,103 @@
+package org.apache.poi.hssf.usermodel;\r
+/* ====================================================================\r
+   Licensed to the Apache Software Foundation (ASF) under one or more\r
+   contributor license agreements.  See the NOTICE file distributed with\r
+   this work for additional information regarding copyright ownership.\r
+   The ASF licenses this file to You under the Apache License, Version 2.0\r
+   (the "License"); you may not use this file except in compliance with\r
+   the License.  You may obtain a copy of the License at\r
+\r
+       http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+   Unless required by applicable law or agreed to in writing, software\r
+   distributed under the License is distributed on an "AS IS" BASIS,\r
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+   See the License for the specific language governing permissions and\r
+   limitations under the License.\r
+==================================================================== */\r
+\r
+import junit.framework.TestCase;\r
+\r
+import java.io.IOException;\r
+import java.io.FileInputStream;\r
+import java.io.File;\r
+\r
+/**\r
+ * Bug 44297: 32767+32768 is evaluated to -1\r
+ * Fix: IntPtg must operate with unsigned short. Reading signed short results in incorrect formula calculation\r
+ * if a formula has values in the interval [Short.MAX_VALUE, (Short.MAX_VALUE+1)*2]\r
+ *\r
+ * @author Yegor Kozlov\r
+ */\r
+\r
+public class TestBug44297 extends TestCase {\r
+    protected String cwd = System.getProperty("HSSF.testdata.path");\r
+\r
+    public void test44297() throws IOException {\r
+        FileInputStream in = new FileInputStream(new File(cwd, "44297.xls"));\r
+        HSSFWorkbook wb = new HSSFWorkbook(in);\r
+        in.close();\r
+\r
+        HSSFRow row;\r
+        HSSFCell  cell;\r
+\r
+        HSSFSheet sheet   = wb.getSheetAt(0);\r
+\r
+        HSSFFormulaEvaluator eva = new HSSFFormulaEvaluator(sheet, wb);\r
+\r
+        row = (HSSFRow)sheet.getRow(0);\r
+        cell = row.getCell((short)0);\r
+        assertEquals("31+46", cell.getCellFormula());\r
+        eva.setCurrentRow(row);\r
+        assertEquals(77, eva.evaluate(cell).getNumberValue(), 0);\r
+\r
+        row = (HSSFRow)sheet.getRow(1);\r
+        cell = row.getCell((short)0);\r
+        assertEquals("30+53", cell.getCellFormula());\r
+        eva.setCurrentRow(row);\r
+        assertEquals(83, eva.evaluate(cell).getNumberValue(), 0);\r
+\r
+        row = (HSSFRow)sheet.getRow(2);\r
+        cell = row.getCell((short)0);\r
+        assertEquals("SUM(A1:A2)", cell.getCellFormula());\r
+        eva.setCurrentRow(row);\r
+        assertEquals(160, eva.evaluate(cell).getNumberValue(), 0);\r
+\r
+        row = (HSSFRow)sheet.getRow(4);\r
+        cell = row.getCell((short)0);\r
+        assertEquals("32767+32768", cell.getCellFormula());\r
+        eva.setCurrentRow(row);\r
+        assertEquals(65535, eva.evaluate(cell).getNumberValue(), 0);\r
+\r
+        row = (HSSFRow)sheet.getRow(7);\r
+        cell = row.getCell((short)0);\r
+        assertEquals("32744+42333", cell.getCellFormula());\r
+        eva.setCurrentRow(row);\r
+        assertEquals(75077, eva.evaluate(cell).getNumberValue(), 0);\r
+\r
+        row = (HSSFRow)sheet.getRow(8);\r
+        cell = row.getCell((short)0);\r
+        assertEquals("327680.0/32768", cell.getCellFormula());\r
+        eva.setCurrentRow(row);\r
+        assertEquals(10, eva.evaluate(cell).getNumberValue(), 0);\r
+\r
+        row = (HSSFRow)sheet.getRow(9);\r
+        cell = row.getCell((short)0);\r
+        assertEquals("32767+32769", cell.getCellFormula());\r
+        eva.setCurrentRow(row);\r
+        assertEquals(65536, eva.evaluate(cell).getNumberValue(), 0);\r
+\r
+        row = (HSSFRow)sheet.getRow(10);\r
+        cell = row.getCell((short)0);\r
+        assertEquals("35000+36000", cell.getCellFormula());\r
+        eva.setCurrentRow(row);\r
+        assertEquals(71000, eva.evaluate(cell).getNumberValue(), 0);\r
+\r
+        row = (HSSFRow)sheet.getRow(11);\r
+        cell = row.getCell((short)0);\r
+        assertEquals("-1000000.0-3000000.0", cell.getCellFormula());\r
+        eva.setCurrentRow(row);\r
+        assertEquals(-4000000, eva.evaluate(cell).getNumberValue(), 0);\r
+    }\r
+\r
+}\r
index cd2acc7ea9f10787212486b5f80bcdbe8d64caef..6c2e3b6412fb2abec14e08b625217d2476917d36 100644 (file)
@@ -82,7 +82,7 @@ public class TestFormulaEvaluatorDocs extends TestCase {
                assertEquals(HSSFCell.CELL_TYPE_FORMULA, wb.getSheetAt(0).getRow(1).getCell((short)2).getCellType());
                
                assertEquals(22.3, wb.getSheetAt(1).getRow(0).getCell((short)0).getNumericCellValue(), 0);
-               assertEquals("S1!A1", wb.getSheetAt(1).getRow(0).getCell((short)0).getCellFormula());
+               assertEquals("'S1'!A1", wb.getSheetAt(1).getRow(0).getCell((short)0).getCellFormula());
                assertEquals(HSSFCell.CELL_TYPE_FORMULA, wb.getSheetAt(1).getRow(0).getCell((short)0).getCellType());
                
                
diff --git a/src/scratchpad/testcases/org/apache/poi/hwpf/data/Bug44292.doc b/src/scratchpad/testcases/org/apache/poi/hwpf/data/Bug44292.doc
new file mode 100644 (file)
index 0000000..fd7ca6c
Binary files /dev/null and b/src/scratchpad/testcases/org/apache/poi/hwpf/data/Bug44292.doc differ
index 8e7f47ed96cb3e0652386b69ebf0f6bc094430a0..e82c4d13045f98f4277d6065a90fd59d807e087f 100644 (file)
@@ -74,4 +74,34 @@ public class TestProblems extends TestCase {
                }
        }
        }
+
+       /**
+        * Test for TableCell not skipping the last paragraph
+        */
+       public void testTableCellLastParagraph() throws Exception {
+       HWPFDocument doc = new HWPFDocument(new FileInputStream(dirname + "/Bug44292.doc"));
+               Range r = doc.getRange();
+                       
+               //get the table
+               Paragraph p = r.getParagraph(0);
+               Table t = r.getTable(p);
+               
+               //get the only row
+               TableRow row = t.getRow(0);
+               
+               //get the first cell
+               TableCell cell = row.getCell(0);
+               // First cell should have one paragraph
+               assertEquals(1, cell.numParagraphs());
+               
+               //get the second
+               cell = row.getCell(1);
+               // Second cell should be detected as having two paragraphs
+               assertEquals(2, cell.numParagraphs());
+                               
+               //get the last cell
+               cell = row.getCell(2);
+               // Last cell should have one paragraph
+               assertEquals(1, cell.numParagraphs());
+       }
 }
index 1bc9df179844bcce13f6af0e2b0dfef8a92e11aa..1e0edd6825a7300c820f3259f91e3b9885420bef 100644 (file)
@@ -75,13 +75,7 @@ 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.TestAreaErrPtg;
-import org.apache.poi.hssf.record.formula.TestErrPtg;
-import org.apache.poi.hssf.record.formula.TestFuncPtg;
-import org.apache.poi.hssf.record.formula.TestIntersectionPtg;
-import org.apache.poi.hssf.record.formula.TestPercentPtg;
-import org.apache.poi.hssf.record.formula.TestRangePtg;
-import org.apache.poi.hssf.record.formula.TestUnionPtg;
+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;
@@ -215,13 +209,7 @@ public class HSSFTests
         suite.addTest(new TestSuite(TestSheetReferences.class));
         
         
-        suite.addTest(new TestSuite(TestAreaErrPtg.class));
-        suite.addTest(new TestSuite(TestErrPtg.class));
-        suite.addTest(new TestSuite(TestFuncPtg.class));
-        suite.addTest(new TestSuite(TestIntersectionPtg.class));
-        suite.addTest(new TestSuite(TestPercentPtg.class));
-        suite.addTest(new TestSuite(TestRangePtg.class));
-        suite.addTest(new TestSuite(TestUnionPtg.class));
+        suite.addTest(AllFormulaTests.suite());
                  suite.addTest(new TestSuite(TestValueRecordsAggregate.class));
                  suite.addTest(new TestSuite(TestNameRecord.class));
                   suite.addTest(new TestSuite(TestEventRecordFactory.class));
diff --git a/src/testcases/org/apache/poi/hssf/data/HyperlinksOnManySheets.xls b/src/testcases/org/apache/poi/hssf/data/HyperlinksOnManySheets.xls
new file mode 100755 (executable)
index 0000000..1884e57
Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/HyperlinksOnManySheets.xls differ
diff --git a/src/testcases/org/apache/poi/hssf/data/SimpleWithChoose.xls b/src/testcases/org/apache/poi/hssf/data/SimpleWithChoose.xls
new file mode 100755 (executable)
index 0000000..96a8e74
Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/SimpleWithChoose.xls differ
index a9460375cb21958270dc7234ef79e5152de86bab..0b2a86948605b08693921925b3d5d6fe708872c6 100644 (file)
Binary files a/src/testcases/org/apache/poi/hssf/data/TestDataValidation.xls and b/src/testcases/org/apache/poi/hssf/data/TestDataValidation.xls differ
diff --git a/src/testcases/org/apache/poi/hssf/data/WithHyperlink.xls b/src/testcases/org/apache/poi/hssf/data/WithHyperlink.xls
new file mode 100644 (file)
index 0000000..e136506
Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/WithHyperlink.xls differ
diff --git a/src/testcases/org/apache/poi/hssf/data/WithTwoHyperLinks.xls b/src/testcases/org/apache/poi/hssf/data/WithTwoHyperLinks.xls
new file mode 100644 (file)
index 0000000..6ee60b5
Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/WithTwoHyperLinks.xls differ
index bd936a0afce86cf15df87e6b5fcc2dc4388a82c7..049b43ef932450e99cefe16a65ae8722e41f3114 100644 (file)
@@ -23,8 +23,13 @@ import java.io.File;
 import java.io.FileInputStream;
 import java.util.ArrayList;
 
+import org.apache.poi.hssf.record.DVALRecord;
+import org.apache.poi.hssf.record.DVRecord;
+import org.apache.poi.hssf.record.EOFRecord;
 import org.apache.poi.hssf.record.Record;
 import org.apache.poi.hssf.record.ContinueRecord;
+import org.apache.poi.hssf.record.SelectionRecord;
+import org.apache.poi.hssf.record.WindowTwoRecord;
 import org.apache.poi.poifs.filesystem.POIFSFileSystem;
 
 import junit.framework.TestCase;
@@ -48,7 +53,15 @@ public class TestHSSFEventFactory extends TestCase {
                factory.processWorkbookEvents(req, fs);
 
                // Check we got the records
+               System.out.println("Processed, found " + mockListen.records.size() + " records");
                assertTrue( mockListen.records.size() > 100 );
+               
+               // Check that the last few records are as we expect
+               // (Makes sure we don't accidently skip the end ones)
+               int numRec = mockListen.records.size();
+               assertEquals(WindowTwoRecord.class, mockListen.records.get(numRec-3).getClass());
+               assertEquals(SelectionRecord.class, mockListen.records.get(numRec-2).getClass());
+               assertEquals(EOFRecord.class,       mockListen.records.get(numRec-1).getClass());
        }
 
        public void testWithCrazyContinueRecords() throws Exception {
@@ -66,6 +79,7 @@ public class TestHSSFEventFactory extends TestCase {
                factory.processWorkbookEvents(req, fs);
 
                // Check we got the records
+               System.out.println("Processed, found " + mockListen.records.size() + " records");
                assertTrue( mockListen.records.size() > 100 );
 
                // And none of them are continue ones
@@ -74,6 +88,13 @@ public class TestHSSFEventFactory extends TestCase {
                for(int i=0; i<r.length; i++) {
                        assertFalse( r[i] instanceof ContinueRecord );
                }
+               
+               // Check that the last few records are as we expect
+               // (Makes sure we don't accidently skip the end ones)
+               int numRec = mockListen.records.size();
+               assertEquals(DVALRecord.class, mockListen.records.get(numRec-3).getClass());
+               assertEquals(DVRecord.class, mockListen.records.get(numRec-2).getClass());
+               assertEquals(EOFRecord.class,       mockListen.records.get(numRec-1).getClass());
        }
 
     /**
index 9e6b53e9af9eb7fec82b701bee9b560c99ce70ca..1b41d87d4d97baccfe8c6d06a21af6dc1fc5ebbe 100644 (file)
@@ -379,15 +379,16 @@ public class TestFormulaParser extends TestCase {
                fp = new FormulaParser("40000", null);
                fp.parse();
                ptg=fp.getRPNPtg();
-               assertTrue("ptg should be  Number, is "+ptg[0].getClass(), ptg[0] instanceof NumberPtg);
+               assertTrue("ptg should be  IntPtg, is "+ptg[0].getClass(), ptg[0] instanceof IntPtg);
        }
+
        /** bug 33160, testcase by Amol Deshmukh*/
        public void testSimpleLongFormula() {
                        FormulaParser fp = new FormulaParser("40000/2", null);
                        fp.parse();
                        Ptg[] ptgs = fp.getRPNPtg();
                        assertTrue("three tokens expected, got "+ptgs.length,ptgs.length == 3);
-                       assertTrue("NumberPtg",(ptgs[0] instanceof NumberPtg));
+                       assertTrue("IntPtg",(ptgs[0] instanceof IntPtg));
                        assertTrue("IntPtg",(ptgs[1] instanceof IntPtg));
                        assertTrue("DividePtg",(ptgs[2] instanceof DividePtg));
        }
index 99026bb8064f9d7fed194994beda8d8c25739d75..9cf746ce00de60d1706c5af9c60872830cbc71b7 100644 (file)
 package org.apache.poi.hssf.record;
 
 
+import java.io.ByteArrayInputStream;
+
+import org.apache.poi.hssf.record.formula.AttrPtg;
+import org.apache.poi.hssf.record.formula.ConcatPtg;
+import org.apache.poi.hssf.record.formula.FuncVarPtg;
+import org.apache.poi.hssf.record.formula.IntPtg;
+import org.apache.poi.hssf.record.formula.RangePtg;
+import org.apache.poi.hssf.record.formula.ReferencePtg;
+import org.apache.poi.hssf.record.formula.UnknownPtg;
+
 import junit.framework.TestCase;
 
 /**
@@ -108,6 +118,52 @@ public class TestFormulaRecord
        assertEquals("Offset 22", 1, output[26]);
     }
     
+    public void testWithConcat()  throws Exception {
+       // =CHOOSE(2,A2,A3,A4)
+       byte[] data = new byte[] {
+                       6, 0, 68, 0,
+                       1, 0, 1, 0, 15, 0, 0, 0, 0, 0, 0, 0, 57,
+                               64, 0, 0, 12, 0, 12, -4, 46, 0, 
+                               30, 2, 0,    // Int - 2
+                               25, 4, 3, 0, // Attr
+                               8, 0,        // Concat 
+                               17, 0,       // Range 
+                               26, 0, 35, 0, // Bit like an attr
+                               36, 1, 0, 0, -64, // Ref - A2
+                               25, 8, 21, 0, // Attr
+                               36, 2, 0, 0, -64, // Ref - A3
+                               25,     8, 12, 0, // Attr
+                               36, 3, 0, 0, -64, // Ref - A4
+                               25, 8, 3, 0,  // Attr 
+                               66, 4, 100, 0 // CHOOSE
+       };
+       RecordInputStream inp = new RecordInputStream(
+                       new ByteArrayInputStream(data)
+       );
+       inp.nextRecord();
+       
+       FormulaRecord fr = new FormulaRecord(inp);
+       
+       assertEquals(14, fr.getNumberOfExpressionTokens());
+       assertEquals(IntPtg.class,       fr.getParsedExpression().get(0).getClass());
+       assertEquals(AttrPtg.class,      fr.getParsedExpression().get(1).getClass());
+       assertEquals(ConcatPtg.class,    fr.getParsedExpression().get(2).getClass());
+       assertEquals(UnknownPtg.class,   fr.getParsedExpression().get(3).getClass());
+       assertEquals(RangePtg.class,     fr.getParsedExpression().get(4).getClass());
+       assertEquals(UnknownPtg.class,   fr.getParsedExpression().get(5).getClass());
+       assertEquals(AttrPtg.class,      fr.getParsedExpression().get(6).getClass());
+       assertEquals(ReferencePtg.class, fr.getParsedExpression().get(7).getClass());
+       assertEquals(AttrPtg.class,      fr.getParsedExpression().get(8).getClass());
+       assertEquals(ReferencePtg.class, fr.getParsedExpression().get(9).getClass());
+       assertEquals(AttrPtg.class,      fr.getParsedExpression().get(10).getClass());
+       assertEquals(ReferencePtg.class, fr.getParsedExpression().get(11).getClass());
+       assertEquals(AttrPtg.class,      fr.getParsedExpression().get(12).getClass());
+       assertEquals(FuncVarPtg.class,   fr.getParsedExpression().get(13).getClass());
+       
+       FuncVarPtg choose = (FuncVarPtg)fr.getParsedExpression().get(13);
+       assertEquals("CHOOSE", choose.getName());
+    }
+    
     
     public static void main(String [] ignored_args)
     {
diff --git a/src/testcases/org/apache/poi/hssf/record/TestHyperlinkRecord.java b/src/testcases/org/apache/poi/hssf/record/TestHyperlinkRecord.java
new file mode 100644 (file)
index 0000000..70548fe
--- /dev/null
@@ -0,0 +1,131 @@
+/* ====================================================================
+   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 java.net.URL;
+
+import junit.framework.TestCase;
+
+public class TestHyperlinkRecord extends TestCase {
+       protected void setUp() throws Exception {
+               super.setUp();
+       }
+
+       private byte[] data = new byte[] { 
+               -72, 1, 110, 0,
+               // ??, Row, col, xf
+               6, 0, 3, 0,     2, 0, 2, 0, 
+               
+               // ??
+               -48, -55, -22, 121, -7, -70, -50, 17, 
+               -116, -126, 0, -86, 0, 75, -87, 11, 
+               2, 0, 0, 0, 
+               
+               // URL length
+               23, 0, 0, 0, 
+               
+               // Label length
+               4, 0, 0, 0,
+               
+               // Label
+               76, 0, 44, 0, 65, 0, 0, 0, 
+               
+               // ??
+               -32, -55, -22, 121, -7, -70, -50, 17,
+               -116, -126, 0, -86, 0, 75, -87, 11, 
+               46, 0, 0, 0,
+               
+               // URL
+               104, 0, 116, 0, 116, 0, 112, 0, 58, 0, 47, 0, 47, 0, 119,
+               0, 119, 0, 119, 0, 46, 0, 108, 0, 97, 0, 107, 0, 105,
+               0, 110, 0, 103, 0, 115, 0, 46, 0, 99, 0, 111, 0,
+               109, 0, 
+               0, 0 };
+       
+       private byte[] data2 = new byte[] {
+               -72, 1, -126, 0,
+               // ??, Row, col, xf
+               2, 0, 2, 0, 4, 0, 4, 0,
+
+               // ??
+               -48, -55, -22, 121, -7, -70, -50, 17,
+               -116, -126, 0, -86, 0, 75, -87, 11,
+               2, 0, 0, 0,
+               
+               // URL and Label lengths
+               23, 0, 0, 0,
+               15, 0, 0, 0,
+
+               // Label
+               83, 0, 116, 0, 97, 0, 99, 0, 105, 0,
+               101, 0, 64, 0, 65, 0, 66, 0, 67, 0,
+               46, 0, 99, 0, 111, 0, 109, 0, 0, 0,
+
+               // ??
+               -32, -55, -22, 121, -7, -70, -50, 17,
+               -116, -126, 0, -86, 0, 75, -87, 11,
+               44, 0, 0, 0,
+
+               // URL
+               109, 0, 97, 0, 105, 0, 108, 0, 116, 0,
+               111, 0, 58, 0, 83, 0, 116, 0, 97, 0,
+               99, 0, 105, 0, 101, 0, 64, 0, 65, 0,
+               66, 0, 67, 0, 46, 0, 99, 0, 111, 0,
+               109, 0, 0, 0 };
+
+       public void testRecordParsing() throws Exception {
+        RecordInputStream inp = new RecordInputStream(
+                new ByteArrayInputStream(data)
+        );
+        inp.nextRecord();
+
+        HyperlinkRecord r = new HyperlinkRecord(inp);
+        
+        assertEquals(3, r.getRow());
+        assertEquals(2, r.getColumn());
+        assertEquals(2, r.getXFIndex());
+        
+        assertEquals("L,A", r.getLabel());
+        assertEquals("http://www.lakings.com", r.getUrlString());
+        assertEquals(new URL("http://www.lakings.com"), r.getUrl());
+        
+        // Check it serialises as expected
+        assertEquals(data.length, r.getRecordSize());
+        byte[] d = r.serialize();
+        assertEquals(data.length, d.length);
+        for(int i=0; i<data.length; i++) {
+               assertEquals(data[i], d[i]);
+        }
+       }
+
+       public void testSecondRecord() throws Exception {
+        RecordInputStream inp = new RecordInputStream(
+                new ByteArrayInputStream(data2)
+        );
+        inp.nextRecord();
+
+        HyperlinkRecord r = new HyperlinkRecord(inp);
+        
+        assertEquals(2, r.getRow());
+        assertEquals(4, r.getColumn());
+        assertEquals(4, r.getXFIndex());
+        
+               assertEquals("Stacie@ABC.com", r.getLabel());
+               assertEquals("mailto:Stacie@ABC.com", r.getUrlString());
+       }
+}
index 316dfb348e8ec713b0950149d4cb06ca7652e16c..21e6a8ba3c3d2a12ad8610ac5b18ec4b25f36b6a 100644 (file)
@@ -26,6 +26,7 @@ import java.io.InputStream;
 
 import junit.framework.TestCase;
 
+import org.apache.poi.hssf.model.Workbook;
 import org.apache.poi.hssf.usermodel.HSSFWorkbook;
 
 /**
@@ -34,7 +35,7 @@ import org.apache.poi.hssf.usermodel.HSSFWorkbook;
  *
  * @author Daniel Noll (daniel at nuix dot com dot au)
  */
-public class AbstractPtgTestCase extends TestCase
+public abstract class AbstractPtgTestCase extends TestCase
 {
     /** Directory containing the test data. */
     private static String dataDir = System.getProperty("HSSF.testdata.path");
@@ -46,7 +47,7 @@ public class AbstractPtgTestCase extends TestCase
      * @return the loaded workbook.
      * @throws IOException if an error occurs loading the workbook.
      */
-    protected static HSSFWorkbook loadWorkbook(String filename)
+    protected static final HSSFWorkbook loadWorkbook(String filename)
             throws IOException {
         File file = new File(dataDir, filename);
         InputStream stream = new BufferedInputStream(new FileInputStream(file));
@@ -59,4 +60,18 @@ public class AbstractPtgTestCase extends TestCase
             stream.close();
         }
     }
+    
+    /**
+     * Creates a new Workbook and adds one sheet with the specified name
+     */
+    protected static final Workbook createWorkbookWithSheet(String sheetName) {
+               
+               Workbook book = Workbook.createWorkbook();
+               // this creates sheet if it doesn't exist
+               book.checkExternSheet(0);
+               // TODO - this call alone does not create the sheet even though the javadoc says it does
+               book.setSheetName(0, sheetName); 
+               return book;
+       }
+    
 }
diff --git a/src/testcases/org/apache/poi/hssf/record/formula/AllFormulaTests.java b/src/testcases/org/apache/poi/hssf/record/formula/AllFormulaTests.java
new file mode 100644 (file)
index 0000000..b126813
--- /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.hssf.record.formula;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Collects all tests for this package.
+ * 
+ * @author Josh Micich
+ */
+public class AllFormulaTests {
+       
+       public static Test suite() {
+               TestSuite result = new TestSuite("Tests for org.apache.poi.hssf.record.formula");
+               result.addTestSuite(TestArea3DPtg.class);
+               result.addTestSuite(TestAreaErrPtg.class);
+        result.addTestSuite(TestAreaPtg.class);
+               result.addTestSuite(TestErrPtg.class);
+               result.addTestSuite(TestFuncPtg.class);
+               result.addTestSuite(TestIntersectionPtg.class);
+               result.addTestSuite(TestPercentPtg.class);
+               result.addTestSuite(TestRangePtg.class);
+               result.addTestSuite(TestRef3DPtg.class);
+               result.addTestSuite(TestReferencePtg.class);
+               result.addTestSuite(TestSheetNameFormatter.class);
+               result.addTestSuite(TestUnionPtg.class);
+               return result;
+       }
+}
diff --git a/src/testcases/org/apache/poi/hssf/record/formula/TestArea3DPtg.java b/src/testcases/org/apache/poi/hssf/record/formula/TestArea3DPtg.java
new file mode 100644 (file)
index 0000000..2af50d4
--- /dev/null
@@ -0,0 +1,50 @@
+/* ====================================================================
+   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 org.apache.poi.hssf.model.Workbook;
+
+/**
+ * Tests for Area3DPtg
+ * 
+ * @author Josh Micich
+ */
+public final class TestArea3DPtg extends AbstractPtgTestCase {
+
+    /**
+     * confirms that sheet names get properly escaped
+     */
+       public void testToFormulaString() {
+               
+               Area3DPtg target = new Area3DPtg("A1:B1", (short)0);
+               
+               String sheetName = "my sheet";
+               Workbook book = createWorkbookWithSheet(sheetName);
+               assertEquals("'my sheet'!A1:B1", target.toFormulaString(book));
+               
+        book.setSheetName(0, "Sheet1");
+        assertEquals("Sheet1!A1:B1", target.toFormulaString(book));
+        
+        book.setSheetName(0, "C64");
+        assertEquals("'C64'!A1:B1", target.toFormulaString(book));
+       }
+
+
+
+}
diff --git a/src/testcases/org/apache/poi/hssf/record/formula/TestAreaPtg.java b/src/testcases/org/apache/poi/hssf/record/formula/TestAreaPtg.java
new file mode 100644 (file)
index 0000000..522a5bc
--- /dev/null
@@ -0,0 +1,114 @@
+        
+/* ====================================================================
+   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 junit.framework.TestCase;
+
+import org.apache.poi.hssf.model.FormulaParser;
+
+/**
+ * Tests for {@link AreaPtg}.
+ *
+ * @author Dmitriy Kumshayev
+ */
+public class TestAreaPtg extends TestCase
+{
+
+       AreaPtg relative;
+       AreaPtg absolute;
+       
+       protected void setUp() throws Exception
+       {
+               super.setUp();
+               short firstRow=5;
+               short lastRow=13;
+               short firstCol=7;
+               short lastCol=17;
+               relative = new AreaPtg(firstRow,lastRow,firstCol,lastCol,true,true,true,true);
+               absolute = new AreaPtg(firstRow,lastRow,firstCol,lastCol,false,false,false,false);
+       }
+
+       public void testSetColumnsAbsolute()
+       {
+               resetColumns(absolute);
+               validateReference(true, absolute);
+       }
+       public void testSetColumnsRelative()
+       {
+               resetColumns(relative);
+               validateReference(false, relative);
+       }
+
+       private void validateReference(boolean abs, AreaPtg ref)
+       {
+               assertEquals("First column reference is not "+(abs?"absolute":"relative"),abs,!ref.isFirstColRelative());
+               assertEquals("Last column reference is not "+(abs?"absolute":"relative"),abs,!ref.isLastColRelative());
+               assertEquals("First row reference is not "+(abs?"absolute":"relative"),abs,!ref.isFirstRowRelative());
+               assertEquals("Last row reference is not "+(abs?"absolute":"relative"),abs,!ref.isLastRowRelative());
+       }
+
+
+       public void resetColumns(AreaPtg aptg)
+       {
+               short fc = aptg.getFirstColumn();
+               short lc = aptg.getLastColumn();
+               aptg.setFirstColumn(fc);
+               aptg.setLastColumn(lc);
+               assertEquals(fc , aptg.getFirstColumn() );
+               assertEquals(lc , aptg.getLastColumn() );
+       }
+       
+       public void testFormulaParser()
+       {
+               String formula1="SUM($E$5:$E$6)";
+               String expectedFormula1="SUM($F$5:$F$6)";
+               String newFormula1 = shiftAllColumnsBy1(formula1);
+               assertEquals("Absolute references changed", expectedFormula1, newFormula1);
+               
+               String formula2="SUM(E5:E6)";
+               String expectedFormula2="SUM(F5:F6)";
+               String newFormula2 = shiftAllColumnsBy1(formula2);
+               assertEquals("Relative references changed", expectedFormula2, newFormula2);
+       }
+       
+       private String shiftAllColumnsBy1(String  formula)
+       {
+               int letUsShiftColumn1By1Column=1;
+               
+               FormulaParser parser = new FormulaParser(formula,null);
+               parser.parse();
+
+               final Ptg[] ptgs = parser.getRPNPtg();
+               for(int i=0; i<ptgs.length; i++)
+               {
+                       Ptg ptg = ptgs[i];
+                       if (ptg instanceof AreaPtg )
+                       {
+                               AreaPtg aptg = (AreaPtg)ptg;
+                               aptg.setFirstColumn((short)(aptg.getFirstColumn()+letUsShiftColumn1By1Column));
+                               aptg.setLastColumn((short)(aptg.getLastColumn()+letUsShiftColumn1By1Column));
+                       }
+               }
+               String newFormula = parser.toFormulaString(ptgs);
+               return newFormula;
+       }
+       
+       
+
+}
diff --git a/src/testcases/org/apache/poi/hssf/record/formula/TestRef3DPtg.java b/src/testcases/org/apache/poi/hssf/record/formula/TestRef3DPtg.java
new file mode 100644 (file)
index 0000000..b5fbe3b
--- /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.hssf.record.formula;
+
+import org.apache.poi.hssf.model.Workbook;
+
+/**
+ * Tests for Ref3DPtg
+ * 
+ * @author Josh Micich
+ */
+public final class TestRef3DPtg extends AbstractPtgTestCase {
+
+       public void testToFormulaString() {
+               
+               Ref3DPtg target = new Ref3DPtg("A1", (short)0);
+               
+               Workbook book = createWorkbookWithSheet("my sheet");
+               
+               assertEquals("'my sheet'!A1", target.toFormulaString(book));
+
+        book.setSheetName(0, "ProfitAndLoss");
+        assertEquals("ProfitAndLoss!A1", target.toFormulaString(book));
+        
+        book.setSheetName(0, "profit+loss");
+        assertEquals("'profit+loss'!A1", target.toFormulaString(book));
+       }
+}
diff --git a/src/testcases/org/apache/poi/hssf/record/formula/TestSheetNameFormatter.java b/src/testcases/org/apache/poi/hssf/record/formula/TestSheetNameFormatter.java
new file mode 100644 (file)
index 0000000..768c429
--- /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.formula;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for SheetNameFormatter
+ * 
+ * @author Josh Micich
+ */
+public final class TestSheetNameFormatter extends TestCase {
+
+       public TestSheetNameFormatter(String testName) {
+               super(testName);
+       }
+       
+       private static void confirmFormat(String rawSheetName, String expectedSheetNameEncoding) {
+               assertEquals(expectedSheetNameEncoding, SheetNameFormatter.format(rawSheetName));
+       }
+
+       /**
+        * Tests main public method 'format' 
+        */
+       public void testFormat() {
+               
+               confirmFormat("abc", "abc");
+               confirmFormat("123", "'123'");
+               
+               confirmFormat("my sheet", "'my sheet'"); // space
+               confirmFormat("A:MEM", "'A:MEM'"); // colon
+
+               confirmFormat("O'Brian", "'O''Brian'"); // single quote gets doubled
+               
+               
+               confirmFormat("3rdTimeLucky", "'3rdTimeLucky'"); // digit in first pos
+               confirmFormat("_", "_"); // plain underscore OK
+               confirmFormat("my_3rd_sheet", "my_3rd_sheet"); // underscores and digits OK
+               confirmFormat("A12220", "'A12220'"); 
+               confirmFormat("TAXRETURN19980415", "TAXRETURN19980415"); 
+       }
+       
+       private static void confirmCellNameMatch(String rawSheetName, boolean expected) {
+               assertEquals(expected, SheetNameFormatter.nameLooksLikePlainCellReference(rawSheetName));
+       }
+       
+       /**
+        * Tests functionality to determine whether a sheet name containing only letters and digits
+        * would look (to Excel) like a cell name.
+        */
+       public void testLooksLikePlainCellReference() {
+               
+               confirmCellNameMatch("A1", true);
+               confirmCellNameMatch("a111", true);
+               confirmCellNameMatch("AA", false);
+               confirmCellNameMatch("aa1", true);
+               confirmCellNameMatch("A1A", false);
+               confirmCellNameMatch("A1A1", false);
+               confirmCellNameMatch("SALES20080101", false); // out of range
+       }
+       
+       private static void confirmCellRange(String text, int numberOfPrefixLetters, boolean expected) {
+               assertEquals(expected, SheetNameFormatter.cellReferenceIsWithinRange(text, numberOfPrefixLetters));
+       }
+       
+       /**
+        * Tests exact boundaries for names that look very close to cell names (i.e. contain 1 or more
+        * letters followed by one or more digits).
+        */
+       public void testCellRange() {
+               confirmCellRange("A1", 1, true);
+               confirmCellRange("a111", 1, true);
+               confirmCellRange("A65536", 1, true);
+               confirmCellRange("A65537", 1, false);
+               confirmCellRange("iv1", 2, true);
+               confirmCellRange("IW1", 2, false);
+               confirmCellRange("AAA1", 3, false);
+               confirmCellRange("a111", 1, true);
+               confirmCellRange("Sheet1", 6, false);
+        confirmCellRange("iV65536", 2, true);  // max cell in Excel 97-2003
+        confirmCellRange("IW65537", 2, false);
+       }
+}
index d0dc7203e1f127ee18b15cc86723e8f31093673c..590ec4943c9c0b262d401ef40e15e046296acead 100644 (file)
@@ -1008,9 +1008,45 @@ extends TestCase {
 
         wb = new HSSFWorkbook(new ByteArrayInputStream(out.toByteArray()));
         assertTrue("No Exceptions while reading file", true);
-
     }
 
+       /**
+        * Bug 42618: RecordFormatException reading a file containing
+        *      =CHOOSE(2,A2,A3,A4)
+        * TODO - support getCellFormula too!
+        */
+    public void test42618() throws Exception {
+        FileInputStream in = new FileInputStream(new File(cwd, "SimpleWithChoose.xls"));
+        HSSFWorkbook wb = new HSSFWorkbook(in);
+        in.close();
+
+        assertTrue("No Exceptions while reading file", true);
+
+        //serialize and read again
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        wb.write(out);
+        out.close();
+
+        wb = new HSSFWorkbook(new ByteArrayInputStream(out.toByteArray()));
+        assertTrue("No Exceptions while reading file", true);
+        
+        // Check we detect the string properly too
+        HSSFSheet s = wb.getSheetAt(0);
+        
+        // Textual value
+        HSSFRow r1 = s.getRow(0);
+        HSSFCell c1 = r1.getCell((short)1);
+        assertEquals("=CHOOSE(2,A2,A3,A4)", c1.getRichStringCellValue().toString());
+        
+        // Formula Value
+        HSSFRow r2 = s.getRow(1);
+        HSSFCell c2 = r2.getCell((short)1);
+        assertEquals(25, (int)c2.getNumericCellValue());
+        
+        // This will blow up with a 
+        //  "EmptyStackException"
+        //assertEquals("=CHOOSE(2,A2,A3,A4)", c2.getCellFormula());
+    }
 }
 
 
index ee3cace263f1c82f1776b434bf228bc6ec85069c..80785ca182d8155a7ae4c03441d6eb3ad75dc3e9 100644 (file)
 
 package org.apache.poi.hssf.usermodel;
 
-import junit.framework.TestCase;
-
-import org.apache.poi.poifs.filesystem.POIFSFileSystem;
-import org.apache.poi.hssf.model.Sheet;
-import org.apache.poi.hssf.record.Record;
-import org.apache.poi.hssf.record.BOFRecord;
-import org.apache.poi.hssf.record.EOFRecord;
-import org.apache.poi.hssf.util.CellReference;
-import org.apache.poi.hssf.util.HSSFColor;
-import org.apache.poi.util.TempFile;
-
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
-
-import java.util.List;
-import java.util.Iterator;
 import java.util.Date;
 import java.util.GregorianCalendar;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+import org.apache.poi.hssf.model.Sheet;
+import org.apache.poi.hssf.record.HyperlinkRecord;
+import org.apache.poi.hssf.util.HSSFColor;
+import org.apache.poi.poifs.filesystem.POIFSFileSystem;
+import org.apache.poi.util.TempFile;
 
 /**
  * Tests various functionity having to do with HSSFCell.  For instance support for
@@ -308,7 +303,54 @@ extends TestCase {
             assertTrue("Bottom Border", (cs.getBorderBottom() == (short)1));
             
             in.close();
-    }    
+    }
+
+    /**
+     * Test reading hyperlinks
+     */
+    public void testWithHyperlink() throws Exception {
+        String dir = System.getProperty("HSSF.testdata.path");
+        File f = new File(dir, "WithHyperlink.xls");
+       HSSFWorkbook wb = new HSSFWorkbook(new FileInputStream(f));
+
+        HSSFSheet sheet = wb.getSheetAt(0);
+        HSSFCell cell = sheet.getRow(4).getCell((short)0);
+        HSSFHyperlink link = cell.getHyperlink();
+        assertNotNull(link);
+
+        assertEquals("Foo", link.getLabel());
+        assertEquals("http://poi.apache.org/", link.getAddress());
+        assertEquals(4, link.getRow());
+        assertEquals(0, link.getColumn());
+    }
+    
+    /**
+     * Test reading hyperlinks
+     */
+    public void testWithTwoHyperlinks() throws Exception {
+        String dir = System.getProperty("HSSF.testdata.path");
+        File f = new File(dir, "WithTwoHyperLinks.xls");
+       HSSFWorkbook wb = new HSSFWorkbook(new FileInputStream(f));
+       
+        HSSFSheet sheet = wb.getSheetAt(0);
+
+        HSSFCell cell1 = sheet.getRow(4).getCell((short)0);
+        HSSFHyperlink link1 = cell1.getHyperlink();
+        assertNotNull(link1);
+        assertEquals("Foo", link1.getLabel());
+        assertEquals("http://poi.apache.org/", link1.getAddress());
+        assertEquals(4, link1.getRow());
+        assertEquals(0, link1.getColumn());
+
+        HSSFCell cell2 = sheet.getRow(8).getCell((short)1);
+        HSSFHyperlink link2 = cell2.getHyperlink();
+        assertNotNull(link2);
+        assertEquals("Bar", link2.getLabel());
+        assertEquals("http://poi.apache.org/", link2.getAddress());
+        assertEquals(8, link2.getRow());
+        assertEquals(1, link2.getColumn());
+
+    }
     
     /*tests the toString() method of HSSFCell*/
     public void testToString() throws Exception {
index f804822d24bc419684ecee442c46889e0416be52..f22de1758cef21b35e731782a87a29eb2a37a253 100644 (file)
@@ -351,7 +351,7 @@ public class TestNamedRange
             String retrievedPrintArea = workbook.getPrintArea(0);
            
                 assertNotNull("Print Area not defined for first sheet", retrievedPrintArea);        
-            assertEquals(reference, retrievedPrintArea);
+            assertEquals("'" + sheetName + "'!$A$1:$B$1", retrievedPrintArea);
             
         }
 
@@ -370,7 +370,7 @@ public class TestNamedRange
                 String retrievedPrintArea = workbook.getPrintArea(0);
            
                 assertNotNull("Print Area not defined for first sheet", retrievedPrintArea);        
-                assertEquals(sheetName+"!"+reference, retrievedPrintArea);
+                assertEquals("'" + sheetName + "'!" + reference, retrievedPrintArea);
             
         }
 
@@ -437,7 +437,7 @@ public class TestNamedRange
             
                String retrievedPrintArea = workbook.getPrintArea(0);       
                assertNotNull("Print Area not defined for first sheet", retrievedPrintArea);        
-               assertEquals("References Match", reference, retrievedPrintArea);
+               assertEquals("References Match", "'" + sheetName + "'!$A$1:$B$1", retrievedPrintArea);
          
        }
 
@@ -449,9 +449,9 @@ public class TestNamedRange
        {
            HSSFWorkbook workbook = new HSSFWorkbook();        
            
-           HSSFSheet sheet = workbook.createSheet("Sheet 1");
-           sheet = workbook.createSheet("Sheet 2");
-           sheet = workbook.createSheet("Sheet 3");
+           HSSFSheet sheet = workbook.createSheet("Sheet1");
+           sheet = workbook.createSheet("Sheet2");
+           sheet = workbook.createSheet("Sheet3");
            
            String sheetName = workbook.getSheetName(0);
                String reference = null;
@@ -508,9 +508,30 @@ public class TestNamedRange
                String retrievedPrintArea = workbook.getPrintArea(0);
            
                assertNotNull("Print Area not defined for first sheet", retrievedPrintArea);        
-               assertEquals(reference, retrievedPrintArea);            
+               assertEquals("'" + sheetName + "'!$A$1:$B$1", retrievedPrintArea);      
     }
-     
+
+    
+    /**
+     * Tests the parsing of union area expressions, and re-display in the presence of sheet names
+     * with special characters.
+     */
+    public void testPrintAreaUnion(){
+               HSSFWorkbook workbook = new HSSFWorkbook();        
+               HSSFSheet sheet = workbook.createSheet("Test Print Area");                
+               String sheetName = workbook.getSheetName(0);
+               
+               String reference =       sheetName +  "!$A$1:$B$1, " + sheetName + "!$D$1:$F$2";
+               String expResult = "'" + sheetName + "'!$A$1:$B$1,'" + sheetName + "'!$D$1:$F$2";
+               workbook.setPrintArea(0, reference);
+                    
+               String retrievedPrintArea = workbook.getPrintArea(0);
+           
+               assertNotNull("Print Area not defined for first sheet", retrievedPrintArea);        
+               assertEquals(expResult, retrievedPrintArea);            
+    }
+    
     /**
      * Verifies an existing print area is deleted
      *