Browse Source

Merged revisions 615190-618235 via svnmerge from

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
tags/REL_3_5_BETA2
Ugo Cei 16 years ago
parent
commit
68f4aa5fbc
68 changed files with 3167 additions and 261 deletions
  1. 2
    1
      build.xml
  2. 11
    1
      src/documentation/content/xdocs/changes.xml
  3. 11
    1
      src/documentation/content/xdocs/status.xml
  4. 3
    0
      src/java/org/apache/poi/hssf/dev/BiffViewer.java
  5. 15
    99
      src/java/org/apache/poi/hssf/eventusermodel/HSSFEventFactory.java
  6. 234
    0
      src/java/org/apache/poi/hssf/eventusermodel/HSSFRecordStream.java
  7. 20
    1
      src/java/org/apache/poi/hssf/model/Workbook.java
  8. 370
    0
      src/java/org/apache/poi/hssf/record/HyperlinkRecord.java
  9. 2
    1
      src/java/org/apache/poi/hssf/record/RecordFactory.java
  10. 1
    1
      src/java/org/apache/poi/hssf/record/RecordInputStream.java
  11. 7
    4
      src/java/org/apache/poi/hssf/record/formula/Area3DPtg.java
  12. 7
    7
      src/java/org/apache/poi/hssf/record/formula/AreaPtg.java
  13. 2
    3
      src/java/org/apache/poi/hssf/record/formula/ConcatPtg.java
  14. 8
    19
      src/java/org/apache/poi/hssf/record/formula/IntPtg.java
  15. 10
    6
      src/java/org/apache/poi/hssf/record/formula/Ptg.java
  16. 3
    3
      src/java/org/apache/poi/hssf/record/formula/RangePtg.java
  17. 24
    11
      src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java
  18. 245
    0
      src/java/org/apache/poi/hssf/record/formula/SheetNameFormatter.java
  19. 1
    1
      src/java/org/apache/poi/hssf/record/formula/UnknownPtg.java
  20. 28
    14
      src/java/org/apache/poi/hssf/usermodel/HSSFCell.java
  21. 3
    3
      src/java/org/apache/poi/hssf/usermodel/HSSFDataFormat.java
  22. 128
    0
      src/java/org/apache/poi/hssf/usermodel/HSSFHyperlink.java
  23. 72
    3
      src/java/org/apache/poi/util/CommonsLogger.java
  24. 20
    5
      src/java/org/apache/poi/util/POILogger.java
  25. 17
    1
      src/java/org/apache/poi/util/SystemOutLogger.java
  26. 14
    6
      src/scratchpad/src/org/apache/poi/hslf/model/Fill.java
  27. 1
    1
      src/scratchpad/src/org/apache/poi/hslf/model/Picture.java
  28. 1
    1
      src/scratchpad/src/org/apache/poi/hslf/model/Shape.java
  29. 7
    0
      src/scratchpad/src/org/apache/poi/hslf/model/Slide.java
  30. 39
    5
      src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Columns.java
  31. 99
    4
      src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Counta.java
  32. 224
    5
      src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Countif.java
  33. 88
    5
      src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Index.java
  34. 39
    5
      src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Rows.java
  35. 1
    1
      src/scratchpad/src/org/apache/poi/hwpf/usermodel/TableRow.java
  36. BIN
      src/scratchpad/testcases/org/apache/poi/hslf/data/44296.ppt
  37. 20
    0
      src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestBugs.java
  38. BIN
      src/scratchpad/testcases/org/apache/poi/hssf/data/44297.xls
  39. 44
    0
      src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java
  40. 63
    0
      src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/EvalFactory.java
  41. 101
    0
      src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/NumericFunctionInvoker.java
  42. 150
    0
      src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestCountFuncs.java
  43. 89
    0
      src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestIndex.java
  44. 102
    0
      src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestRowCol.java
  45. 103
    0
      src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestBug44297.java
  46. 1
    1
      src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestFormulaEvaluatorDocs.java
  47. BIN
      src/scratchpad/testcases/org/apache/poi/hwpf/data/Bug44292.doc
  48. 30
    0
      src/scratchpad/testcases/org/apache/poi/hwpf/usermodel/TestProblems.java
  49. 2
    14
      src/testcases/org/apache/poi/hssf/HSSFTests.java
  50. BIN
      src/testcases/org/apache/poi/hssf/data/HyperlinksOnManySheets.xls
  51. BIN
      src/testcases/org/apache/poi/hssf/data/SimpleWithChoose.xls
  52. BIN
      src/testcases/org/apache/poi/hssf/data/TestDataValidation.xls
  53. BIN
      src/testcases/org/apache/poi/hssf/data/WithHyperlink.xls
  54. BIN
      src/testcases/org/apache/poi/hssf/data/WithTwoHyperLinks.xls
  55. 21
    0
      src/testcases/org/apache/poi/hssf/eventusermodel/TestHSSFEventFactory.java
  56. 3
    2
      src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java
  57. 56
    0
      src/testcases/org/apache/poi/hssf/record/TestFormulaRecord.java
  58. 131
    0
      src/testcases/org/apache/poi/hssf/record/TestHyperlinkRecord.java
  59. 17
    2
      src/testcases/org/apache/poi/hssf/record/formula/AbstractPtgTestCase.java
  60. 47
    0
      src/testcases/org/apache/poi/hssf/record/formula/AllFormulaTests.java
  61. 50
    0
      src/testcases/org/apache/poi/hssf/record/formula/TestArea3DPtg.java
  62. 114
    0
      src/testcases/org/apache/poi/hssf/record/formula/TestAreaPtg.java
  63. 0
    0
      src/testcases/org/apache/poi/hssf/record/formula/TestFuncPtg.java
  64. 44
    0
      src/testcases/org/apache/poi/hssf/record/formula/TestRef3DPtg.java
  65. 99
    0
      src/testcases/org/apache/poi/hssf/record/formula/TestSheetNameFormatter.java
  66. 37
    1
      src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java
  67. 57
    15
      src/testcases/org/apache/poi/hssf/usermodel/TestHSSFCell.java
  68. 29
    8
      src/testcases/org/apache/poi/hssf/usermodel/TestNamedRange.java

+ 2
- 1
build.xml View File

@@ -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}"/>

+ 11
- 1
src/documentation/content/xdocs/changes.xml View File

@@ -35,7 +35,17 @@
</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>

+ 11
- 1
src/documentation/content/xdocs/status.xml View File

@@ -32,7 +32,17 @@

<!-- 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>

+ 3
- 0
src/java/org/apache/poi/hssf/dev/BiffViewer.java View 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 );
}

+ 15
- 99
src/java/org/apache/poi/hssf/eventusermodel/HSSFEventFactory.java View 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;
}
}

+ 234
- 0
src/java/org/apache/poi/hssf/eventusermodel/HSSFRecordStream.java View File

@@ -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;
}
}

+ 20
- 1
src/java/org/apache/poi/hssf/model/Workbook.java View 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();

+ 370
- 0
src/java/org/apache/poi/hssf/record/HyperlinkRecord.java View File

@@ -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;
}
}

+ 2
- 1
src/java/org/apache/poi/hssf/record/RecordFactory.java View 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);

+ 1
- 1
src/java/org/apache/poi/hssf/record/RecordInputStream.java View File

@@ -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);

+ 7
- 4
src/java/org/apache/poi/hssf/record/formula/Area3DPtg.java View 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() );

+ 7
- 7
src/java/org/apache/poi/hssf/record/formula/AreaPtg.java View 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);
}

/**

+ 2
- 3
src/java/org/apache/poi/hssf/record/formula/ConcatPtg.java View 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() {

+ 8
- 19
src/java/org/apache/poi/hssf/record/formula/IntPtg.java View 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()

+ 10
- 6
src/java/org/apache/poi/hssf/record/formula/Ptg.java View 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 + ")");

+ 3
- 3
src/java/org/apache/poi/hssf/record/formula/RangePtg.java View 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 )

+ 24
- 11
src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java View 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();

+ 245
- 0
src/java/org/apache/poi/hssf/record/formula/SheetNameFormatter.java View File

@@ -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());
}

}

+ 1
- 1
src/java/org/apache/poi/hssf/record/formula/UnknownPtg.java View 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 */


+ 28
- 14
src/java/org/apache/poi/hssf/usermodel/HSSFCell.java View 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){

}
}

+ 3
- 3
src/java/org/apache/poi/hssf/usermodel/HSSFDataFormat.java View 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;

+ 128
- 0
src/java/org/apache/poi/hssf/usermodel/HSSFHyperlink.java View File

@@ -0,0 +1,128 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.hssf.usermodel;
import org.apache.poi.hssf.record.EscherAggregate;
import org.apache.poi.hssf.record.NoteRecord;
import org.apache.poi.hssf.record.TextObjectRecord;
import org.apache.poi.hssf.record.HyperlinkRecord;
import org.apache.poi.ddf.*;
import java.util.Map;
import java.util.List;
import java.util.Iterator;
/**
* Represents a hyperlink.
*
* @author Yegor Kozlov
*/
public class HSSFHyperlink {
/**
* Link to a existing file or web page
*/
public static final int LINK_URL = 1;
/**
* Link to a place in this document
*/
public static final int LINK_DOCUMENT = 2;
/**
* Link to an E-mail address
*/
public static final int LINK_EMAIL = 3;
/**
* Unknown type
*/
public static final int LINK_UNKNOWN = 4;
/**
* Low-level record object that stores the actual hyperlink data
*/
private HyperlinkRecord record = null;
protected HSSFHyperlink( HyperlinkRecord record )
{
this.record = record;
}
/**
* Return the row of the cell that contains the hyperlink
*
* @return the 0-based row of the cell that contains the hyperlink
*/
public int getRow(){
return record.getRow();
}
/**
* Set the row of the cell that contains the hyperlink
*
* @param row the 0-based row of the cell that contains the hyperlink
*/
public void setRow(int row){
record.setRow(row);
}
/**
* Return the column of the cell that contains the hyperlink
*
* @return the 0-based column of the cell that contains the hyperlink
*/
public short getColumn(){
return record.getColumn();
}
/**
* Set the column of the cell that contains the hyperlink
*
* @param col the 0-based column of the cell that contains the hyperlink
*/
public void setColumn(short col){
record.setColumn(col);
}
/**
* Hypelink address. Depending on the hyperlink type it can be URL, e-mail, etc.
*
* @return the address of this hyperlink
*/
public String getAddress(){
return record.getUrlString();
}
/**
* Return text to display for this hyperlink
*
* @return text to display
*/
public String getLabel(){
return record.getLabel();
}
/**
* Return the type of this hyperlink
*
* @return the type of this hyperlink
*/
public int getType(){
throw new RuntimeException("Not implemented");
}
}

+ 72
- 3
src/java/org/apache/poi/util/CommonsLogger.java View 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);
}
}

}


+ 20
- 5
src/java/org/apache/poi/util/POILogger.java View 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);
}

/**

+ 17
- 1
src/java/org/apache/poi/util/SystemOutLogger.java View 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);
}
}
}

/**

+ 14
- 6
src/scratchpad/src/org/apache/poi/hslf/model/Fill.java View 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;
}

/**

+ 1
- 1
src/scratchpad/src/org/apache/poi/hslf/model/Picture.java View 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();
}


+ 1
- 1
src/scratchpad/src/org/apache/poi/hslf/model/Shape.java View 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;

+ 7
- 0
src/scratchpad/src/org/apache/poi/hslf/model/Slide.java View 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();
}
}

+ 39
- 5
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Columns.java View File

@@ -14,12 +14,46 @@
* 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);
}
}

+ 99
- 4
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Counta.java View File

@@ -14,12 +14,107 @@
* 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;
}
}

+ 224
- 5
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Countif.java View File

@@ -14,12 +14,231 @@
* 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;
}
}

+ 88
- 5
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Index.java View File

@@ -14,12 +14,95 @@
* 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;
}
}

+ 39
- 5
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Rows.java View File

@@ -14,12 +14,46 @@
* 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);
}
}

+ 1
- 1
src/scratchpad/src/org/apache/poi/hwpf/usermodel/TableRow.java View 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]);

BIN
src/scratchpad/testcases/org/apache/poi/hslf/data/44296.ppt View File


+ 20
- 0
src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestBugs.java View File

@@ -330,4 +330,24 @@ public class TestBugs extends TestCase {
assertEquals(tr1[i].getText(), tr2[i].getText());
}
}
/**
* Bug 44296: HSLF Not Extracting Slide Background Image
*/
public void test44296 () throws Exception {
FileInputStream is = new FileInputStream(new File(cwd, "44296.ppt"));
SlideShow ppt = new SlideShow(is);
is.close();
Slide slide = ppt.getSlides()[0];
Background b = slide.getBackground();
Fill f = b.getFill();
assertEquals(Fill.FILL_PICTURE, f.getFillType());
PictureData pict = f.getPictureData();
assertNotNull(pict);
assertEquals(Picture.JPEG, pict.getType());
}
}

BIN
src/scratchpad/testcases/org/apache/poi/hssf/data/44297.xls View File


+ 44
- 0
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java View File

@@ -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;
}

}

+ 63
- 0
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/EvalFactory.java View File

@@ -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);
}
}

+ 101
- 0
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/NumericFunctionInvoker.java View File

@@ -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();
}

}

+ 150
- 0
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestCountFuncs.java View File

@@ -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);
}
}

+ 89
- 0
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestIndex.java View File

@@ -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);
}
}

+ 102
- 0
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestRowCol.java View File

@@ -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);
}
}

+ 103
- 0
src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestBug44297.java View File

@@ -0,0 +1,103 @@
package org.apache.poi.hssf.usermodel;
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
import junit.framework.TestCase;
import java.io.IOException;
import java.io.FileInputStream;
import java.io.File;
/**
* Bug 44297: 32767+32768 is evaluated to -1
* Fix: IntPtg must operate with unsigned short. Reading signed short results in incorrect formula calculation
* if a formula has values in the interval [Short.MAX_VALUE, (Short.MAX_VALUE+1)*2]
*
* @author Yegor Kozlov
*/
public class TestBug44297 extends TestCase {
protected String cwd = System.getProperty("HSSF.testdata.path");
public void test44297() throws IOException {
FileInputStream in = new FileInputStream(new File(cwd, "44297.xls"));
HSSFWorkbook wb = new HSSFWorkbook(in);
in.close();
HSSFRow row;
HSSFCell cell;
HSSFSheet sheet = wb.getSheetAt(0);
HSSFFormulaEvaluator eva = new HSSFFormulaEvaluator(sheet, wb);
row = (HSSFRow)sheet.getRow(0);
cell = row.getCell((short)0);
assertEquals("31+46", cell.getCellFormula());
eva.setCurrentRow(row);
assertEquals(77, eva.evaluate(cell).getNumberValue(), 0);
row = (HSSFRow)sheet.getRow(1);
cell = row.getCell((short)0);
assertEquals("30+53", cell.getCellFormula());
eva.setCurrentRow(row);
assertEquals(83, eva.evaluate(cell).getNumberValue(), 0);
row = (HSSFRow)sheet.getRow(2);
cell = row.getCell((short)0);
assertEquals("SUM(A1:A2)", cell.getCellFormula());
eva.setCurrentRow(row);
assertEquals(160, eva.evaluate(cell).getNumberValue(), 0);
row = (HSSFRow)sheet.getRow(4);
cell = row.getCell((short)0);
assertEquals("32767+32768", cell.getCellFormula());
eva.setCurrentRow(row);
assertEquals(65535, eva.evaluate(cell).getNumberValue(), 0);
row = (HSSFRow)sheet.getRow(7);
cell = row.getCell((short)0);
assertEquals("32744+42333", cell.getCellFormula());
eva.setCurrentRow(row);
assertEquals(75077, eva.evaluate(cell).getNumberValue(), 0);
row = (HSSFRow)sheet.getRow(8);
cell = row.getCell((short)0);
assertEquals("327680.0/32768", cell.getCellFormula());
eva.setCurrentRow(row);
assertEquals(10, eva.evaluate(cell).getNumberValue(), 0);
row = (HSSFRow)sheet.getRow(9);
cell = row.getCell((short)0);
assertEquals("32767+32769", cell.getCellFormula());
eva.setCurrentRow(row);
assertEquals(65536, eva.evaluate(cell).getNumberValue(), 0);
row = (HSSFRow)sheet.getRow(10);
cell = row.getCell((short)0);
assertEquals("35000+36000", cell.getCellFormula());
eva.setCurrentRow(row);
assertEquals(71000, eva.evaluate(cell).getNumberValue(), 0);
row = (HSSFRow)sheet.getRow(11);
cell = row.getCell((short)0);
assertEquals("-1000000.0-3000000.0", cell.getCellFormula());
eva.setCurrentRow(row);
assertEquals(-4000000, eva.evaluate(cell).getNumberValue(), 0);
}
}

+ 1
- 1
src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestFormulaEvaluatorDocs.java View 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());

BIN
src/scratchpad/testcases/org/apache/poi/hwpf/data/Bug44292.doc View File


+ 30
- 0
src/scratchpad/testcases/org/apache/poi/hwpf/usermodel/TestProblems.java View 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());
}
}

+ 2
- 14
src/testcases/org/apache/poi/hssf/HSSFTests.java View 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));

BIN
src/testcases/org/apache/poi/hssf/data/HyperlinksOnManySheets.xls View File


BIN
src/testcases/org/apache/poi/hssf/data/SimpleWithChoose.xls View File


BIN
src/testcases/org/apache/poi/hssf/data/TestDataValidation.xls View File


BIN
src/testcases/org/apache/poi/hssf/data/WithHyperlink.xls View File


BIN
src/testcases/org/apache/poi/hssf/data/WithTwoHyperLinks.xls View File


+ 21
- 0
src/testcases/org/apache/poi/hssf/eventusermodel/TestHSSFEventFactory.java View 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());
}

/**

+ 3
- 2
src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java View 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));
}

+ 56
- 0
src/testcases/org/apache/poi/hssf/record/TestFormulaRecord.java View File

@@ -21,6 +21,16 @@
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)
{

+ 131
- 0
src/testcases/org/apache/poi/hssf/record/TestHyperlinkRecord.java View File

@@ -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());
}
}

+ 17
- 2
src/testcases/org/apache/poi/hssf/record/formula/AbstractPtgTestCase.java View 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;
}
}

+ 47
- 0
src/testcases/org/apache/poi/hssf/record/formula/AllFormulaTests.java View File

@@ -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;
}
}

+ 50
- 0
src/testcases/org/apache/poi/hssf/record/formula/TestArea3DPtg.java View File

@@ -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));
}



}

+ 114
- 0
src/testcases/org/apache/poi/hssf/record/formula/TestAreaPtg.java View File

@@ -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;
}

}

+ 0
- 0
src/testcases/org/apache/poi/hssf/record/formula/TestFuncPtg.java View File


+ 44
- 0
src/testcases/org/apache/poi/hssf/record/formula/TestRef3DPtg.java View File

@@ -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));
}
}

+ 99
- 0
src/testcases/org/apache/poi/hssf/record/formula/TestSheetNameFormatter.java View File

@@ -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);
}
}

+ 37
- 1
src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java View 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());
}
}



+ 57
- 15
src/testcases/org/apache/poi/hssf/usermodel/TestHSSFCell.java View File

@@ -19,25 +19,20 @@

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 {

+ 29
- 8
src/testcases/org/apache/poi/hssf/usermodel/TestNamedRange.java View 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
*

Loading…
Cancel
Save