From: Ugo Cei Date: Mon, 4 Feb 2008 16:55:43 +0000 (+0000) Subject: Merged revisions 615190-618235 via svnmerge from X-Git-Tag: REL_3_5_BETA2~218 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=68f4aa5fbcee89eef4be77d7a2a9e518fb29d6dc;p=poi.git 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 --- diff --git a/build.xml b/build.xml index 4e33434f4f..8905dc13f5 100644 --- a/build.xml +++ b/build.xml @@ -167,7 +167,7 @@ under the License. - + + diff --git a/src/documentation/content/xdocs/changes.xml b/src/documentation/content/xdocs/changes.xml index 51aeb22461..df0e5e51ad 100644 --- a/src/documentation/content/xdocs/changes.xml +++ b/src/documentation/content/xdocs/changes.xml @@ -35,7 +35,17 @@ - + + 44345 - Implement CountA, CountIf, Index, Rows and Columns functions + 44336 - Properly escape sheet names as required when figuring out the text of formulas + 44326 - Improvements to how SystemOutLogger and CommonsLogger log messages with exceptions, and avoid an infinite loop with certain log messages with exceptions + 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 + + + 44297 - IntPtg must operate with unsigned short. Reading signed short results in incorrect formula calculation + 44296 - Fix for reading slide background images + 44293 - Avoid swapping AreaPtgs from relative to absolute + 44292 - Correctly process the last paragraph in a word file 44254 - Avoid some unread byte warnings, and properly understand DVALRecord Add another formula evaluation method, evaluateFormulaCell(cell), which will re-calculate the value for a formula, without affecting the formula itself. 41726 - Fix how we handle signed cell offsets in relative areas and references diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index feabdf76b4..9a7a3f43cb 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -32,7 +32,17 @@ - + + 44345 - Implement CountA, CountIf, Index, Rows and Columns functions + 44336 - Properly escape sheet names as required when figuring out the text of formulas + 44326 - Improvements to how SystemOutLogger and CommonsLogger log messages with exceptions, and avoid an infinite loop with certain log messages with exceptions + 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 + + + 44297 - IntPtg must operate with unsigned short. Reading signed short results in incorrect formula calculation + 44296 - Fix for reading slide background images + 44293 - Avoid swapping AreaPtgs from relative to absolute + 44292 - Correctly process the last paragraph in a word file 44254 - Avoid some unread byte warnings, and properly understand DVALRecord Add another formula evaluation method, evaluateFormulaCell(cell), which will re-calculate the value for a formula, without affecting the formula itself. 41726 - Fix how we handle signed cell offsets in relative areas and references diff --git a/src/java/org/apache/poi/hssf/dev/BiffViewer.java b/src/java/org/apache/poi/hssf/dev/BiffViewer.java index 2e343012d4..242a85f45f 100644 --- a/src/java/org/apache/poi/hssf/dev/BiffViewer.java +++ b/src/java/org/apache/poi/hssf/dev/BiffViewer.java @@ -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 ); } diff --git a/src/java/org/apache/poi/hssf/eventusermodel/HSSFEventFactory.java b/src/java/org/apache/poi/hssf/eventusermodel/HSSFEventFactory.java index 70c989c179..f8e64c928e 100644 --- a/src/java/org/apache/poi/hssf/eventusermodel/HSSFEventFactory.java +++ b/src/java/org/apache/poi/hssf/eventusermodel/HSSFEventFactory.java @@ -129,109 +129,25 @@ public class HSSFEventFactory protected short genericProcessEvents(HSSFRequest req, RecordInputStream in) throws IOException, HSSFUserException { + boolean going = true; short userCode = 0; - - short sid = 0; - process: - { - - Record rec = null; - Record lastRec = null; - DrawingRecord lastDrawingRecord = new DrawingRecord(); - - while (in.hasNextRecord()) - { - in.nextRecord(); - sid = in.getSid();; - - // - // for some reasons we have to make the workbook to be at least 4096 bytes - // but if we have such workbook we fill the end of it with zeros (many zeros) - // - // it is not good: - // if the length( all zero records ) % 4 = 1 - // e.g.: any zero record would be readed as 4 bytes at once ( 2 - id and 2 - size ). - // And the last 1 byte will be readed WRONG ( the id must be 2 bytes ) - // - // So we should better to check if the sid is zero and not to read more data - // The zero sid shows us that rest of the stream data is a fake to make workbook - // certain size - // - if ( sid == 0 ) - break; - - - if ((rec != null) && (sid != ContinueRecord.sid)) - { - userCode = req.processRecord(rec); - if (userCode != 0) break process; - } - if (sid != ContinueRecord.sid) - { - //System.out.println("creating "+sid); - Record[] recs = RecordFactory.createRecord(in); - - if (recs.length > 1) - { // we know that the multiple - for (int k = 0; k < (recs.length - 1); k++) - { // record situations do not - userCode = req.processRecord( - recs[ k ]); // contain continue records - if (userCode != 0) break process; - } - } - rec = recs[ recs.length - 1 ]; // regardless we'll process - - // the last record as though - // it might be continued - // if there is only one - // records, it will go here too. - } - else { - // Normally, ContinueRecords are handled internally - // However, in a few cases, there is a gap between a record at - // its Continue, so we have to handle them specially - // This logic is much like in RecordFactory.createRecords() - Record[] recs = RecordFactory.createRecord(in); - ContinueRecord crec = (ContinueRecord)recs[0]; - if((lastRec instanceof ObjRecord) || (lastRec instanceof TextObjectRecord)) { - // You can have Obj records between a DrawingRecord - // and its continue! - lastDrawingRecord.processContinueRecord( crec.getData() ); - // Trigger them on the drawing record, now it's complete - rec = lastDrawingRecord; - } - else if((lastRec instanceof DrawingGroupRecord)) { - ((DrawingGroupRecord)lastRec).processContinueRecord(crec.getData()); - // Trigger them on the drawing record, now it's complete - rec = lastRec; - } - else { - if (rec instanceof UnknownRecord) { - ;//silently skip records we don't know about - } else { - throw new RecordFormatException("Records should handle ContinueRecord internally. Should not see this exception"); - } - } - } - - // Update our tracking of the last record - lastRec = rec; - if(rec instanceof DrawingRecord) { - lastDrawingRecord = (DrawingRecord)rec; - } - } - if (rec != null) - { - userCode = req.processRecord(rec); - if (userCode != 0) break process; + Record r = null; + + // Create a new RecordStream and use that + HSSFRecordStream recordStream = new HSSFRecordStream(in); + + // Process each record as they come in + while(going) { + r = recordStream.nextRecord(); + if(r != null) { + userCode = req.processRecord(r); + if (userCode != 0) break; + } else { + going = false; } } + // All done, return our last code return userCode; - - // Record[] retval = new Record[ records.size() ]; - // retval = ( Record [] ) records.toArray(retval); - // return null; } } diff --git a/src/java/org/apache/poi/hssf/eventusermodel/HSSFRecordStream.java b/src/java/org/apache/poi/hssf/eventusermodel/HSSFRecordStream.java new file mode 100644 index 0000000000..feb7a36d5c --- /dev/null +++ b/src/java/org/apache/poi/hssf/eventusermodel/HSSFRecordStream.java @@ -0,0 +1,234 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ +package org.apache.poi.hssf.eventusermodel; + +import java.util.Vector; + +import org.apache.poi.hssf.record.ContinueRecord; +import org.apache.poi.hssf.record.DrawingGroupRecord; +import org.apache.poi.hssf.record.DrawingRecord; +import org.apache.poi.hssf.record.ObjRecord; +import org.apache.poi.hssf.record.Record; +import org.apache.poi.hssf.record.RecordFactory; +import org.apache.poi.hssf.record.RecordFormatException; +import org.apache.poi.hssf.record.RecordInputStream; +import org.apache.poi.hssf.record.TextObjectRecord; +import org.apache.poi.hssf.record.UnknownRecord; + +/** + * A stream based way to get at complete records, with + * as low a memory footprint as possible. + * This handles reading from a RecordInputStream, turning + * the data into full records, processing continue records + * etc. + * Most users should use {@link HSSFEventFactory} / + * {@link HSSFListener} and have new records pushed to + * them, but this does allow for a "pull" style of coding. + */ +public class HSSFRecordStream { + private RecordInputStream in; + + /** Have we run out of records on the stream? */ + private boolean hitEOS = false; + /** Have we returned all the records there are? */ + private boolean complete = false; + + /** + * Sometimes we end up with a bunch of + * records. When we do, these should + * be returned before the next normal + * record processing occurs (i.e. before + * we check for continue records and + * return rec) + */ + private Vector bonusRecords = null; + + /** + * The next record to return, which may need to have its + * continue records passed to it before we do + */ + private Record rec = null; + /** + * The most recent record that we gave to the user + */ + private Record lastRec = null; + /** + * The most recent DrawingRecord seen + */ + private DrawingRecord lastDrawingRecord = new DrawingRecord(); + + public HSSFRecordStream(RecordInputStream inp) { + this.in = inp; + } + + /** + * Returns the next (complete) record from the + * stream, or null if there are no more. + */ + public Record nextRecord() { + Record r = null; + + // Loop until we get something + while(r == null && !complete) { + // Are there any bonus records that we need to + // return? + r = getBonusRecord(); + + // If not, ask for the next real record + if(r == null) { + r = getNextRecord(); + } + } + + // All done + return r; + } + + /** + * If there are any "bonus" records, that should + * be returned before processing new ones, + * grabs the next and returns it. + * If not, returns null; + */ + private Record getBonusRecord() { + if(bonusRecords != null) { + Record r = (Record)bonusRecords.remove(0); + if(bonusRecords.size() == 0) { + bonusRecords = null; + } + return r; + } + return null; + } + + /** + * Returns the next available record, or null if + * this pass didn't return a record that's + * suitable for returning (eg was a continue record). + */ + private Record getNextRecord() { + Record toReturn = null; + + if(in.hasNextRecord()) { + // Grab our next record + in.nextRecord(); + short sid = in.getSid(); + + // + // for some reasons we have to make the workbook to be at least 4096 bytes + // but if we have such workbook we fill the end of it with zeros (many zeros) + // + // it is not good: + // if the length( all zero records ) % 4 = 1 + // e.g.: any zero record would be readed as 4 bytes at once ( 2 - id and 2 - size ). + // And the last 1 byte will be readed WRONG ( the id must be 2 bytes ) + // + // So we should better to check if the sid is zero and not to read more data + // The zero sid shows us that rest of the stream data is a fake to make workbook + // certain size + // + if ( sid == 0 ) + return null; + + + // If we had a last record, and this one + // isn't a continue record, then pass + // it on to the listener + if ((rec != null) && (sid != ContinueRecord.sid)) + { + // This last record ought to be returned + toReturn = rec; + } + + // If this record isn't a continue record, + // then build it up + if (sid != ContinueRecord.sid) + { + //System.out.println("creating "+sid); + Record[] recs = RecordFactory.createRecord(in); + + // We know that the multiple record situations + // don't contain continue records, so just + // pass those on to the listener now + if (recs.length > 1) { + bonusRecords = new Vector(recs.length-1); + for (int k = 0; k < (recs.length - 1); k++) { + bonusRecords.add(recs[k]); + } + } + + // Regardless of the number we created, always hold + // onto the last record to be processed on the next + // loop, in case it has any continue records + rec = recs[ recs.length - 1 ]; + // Don't return it just yet though, as we probably have + // a record from the last round to return + } + else { + // Normally, ContinueRecords are handled internally + // However, in a few cases, there is a gap between a record at + // its Continue, so we have to handle them specially + // This logic is much like in RecordFactory.createRecords() + Record[] recs = RecordFactory.createRecord(in); + ContinueRecord crec = (ContinueRecord)recs[0]; + if((lastRec instanceof ObjRecord) || (lastRec instanceof TextObjectRecord)) { + // You can have Obj records between a DrawingRecord + // and its continue! + lastDrawingRecord.processContinueRecord( crec.getData() ); + // Trigger them on the drawing record, now it's complete + rec = lastDrawingRecord; + } + else if((lastRec instanceof DrawingGroupRecord)) { + ((DrawingGroupRecord)lastRec).processContinueRecord(crec.getData()); + // Trigger them on the drawing record, now it's complete + rec = lastRec; + } + else { + if (rec instanceof UnknownRecord) { + ;//silently skip records we don't know about + } else { + throw new RecordFormatException("Records should handle ContinueRecord internally. Should not see this exception"); + } + } + } + + // Update our tracking of the last record + lastRec = rec; + if(rec instanceof DrawingRecord) { + lastDrawingRecord = (DrawingRecord)rec; + } + } else { + // No more records + hitEOS = true; + } + + // If we've hit the end-of-stream, then + // finish off the last record and be done + if(hitEOS) { + complete = true; + + // Return the last record if there was + // one, otherwise null + if(rec != null) { + toReturn = rec; + rec = null; + } + } + + return toReturn; + } +} \ No newline at end of file diff --git a/src/java/org/apache/poi/hssf/model/Workbook.java b/src/java/org/apache/poi/hssf/model/Workbook.java index 08c236cda1..2ba50857ca 100644 --- a/src/java/org/apache/poi/hssf/model/Workbook.java +++ b/src/java/org/apache/poi/hssf/model/Workbook.java @@ -93,6 +93,8 @@ public class Workbook implements Model protected ArrayList formats = new ArrayList(); protected ArrayList names = new ArrayList(); + + protected ArrayList hyperlinks = new ArrayList(); protected int numxfs = 0; // hold the number of extended format records protected int numfonts = 0; // hold the number of font records @@ -133,7 +135,8 @@ public class Workbook implements Model Workbook retval = new Workbook(); ArrayList records = new ArrayList(recs.size() / 3); - for (int k = 0; k < recs.size(); k++) { + int k; + for (k = 0; k < recs.size(); k++) { Record rec = ( Record ) recs.get(k); if (rec.getSid() == EOFRecord.sid) { @@ -248,6 +251,17 @@ public class Workbook implements Model // retval.records.supbookpos = retval.records.bspos + 1; // retval.records.namepos = retval.records.supbookpos + 1; // } + + // Look for other interesting values that + // follow the EOFRecord + for ( ; k < recs.size(); k++) { + Record rec = ( Record ) recs.get(k); + switch (rec.getSid()) { + case HyperlinkRecord.sid: + retval.hyperlinks.add(rec); + break; + } + } retval.records.setRecords(records); @@ -2116,6 +2130,11 @@ public class Workbook implements Model return null; } + public List getHyperlinks() + { + return hyperlinks; + } + public List getRecords() { return records.getRecords(); diff --git a/src/java/org/apache/poi/hssf/record/HyperlinkRecord.java b/src/java/org/apache/poi/hssf/record/HyperlinkRecord.java new file mode 100644 index 0000000000..0dcd45a724 --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/HyperlinkRecord.java @@ -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 HyperlinkRecord wraps an HLINK-record + * from the Excel-97 format. + * Supports only external links for now (eg http://) + * + * @author Mark Hissink Muller (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 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() diff --git a/src/java/org/apache/poi/hssf/record/formula/Ptg.java b/src/java/org/apache/poi/hssf/record/formula/Ptg.java index cc9a4236ba..f006509c23 100644 --- a/src/java/org/apache/poi/hssf/record/formula/Ptg.java +++ b/src/java/org/apache/poi/hssf/record/formula/Ptg.java @@ -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 + ")"); diff --git a/src/java/org/apache/poi/hssf/record/formula/RangePtg.java b/src/java/org/apache/poi/hssf/record/formula/RangePtg.java index f0bd8c1c1b..51df7844a4 100644 --- a/src/java/org/apache/poi/hssf/record/formula/RangePtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/RangePtg.java @@ -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 ) diff --git a/src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java b/src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java index bc5430bb5d..510eebb037 100644 --- a/src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java @@ -157,18 +157,31 @@ public class Ref3DPtg extends Ptg { } - public String toFormulaString(Workbook book) { + // TODO - find a home for this method + // There is already a method on Workbook called getSheetName but it seems to do something different. + static String getSheetName(Workbook book, int externSheetIndex) { + // TODO - there are 3 ways this method can return null. Is each valid? + if (book == null) { + return null; + } + + SheetReferences refs = book.getSheetReferences(); + if (refs == null) { + return null; + } + return refs.getSheetName(externSheetIndex); + } + /** + * @return text representation of this cell reference that can be used in text + * formulas. The sheet name will get properly delimited if required. + */ + public String toFormulaString(Workbook book) + { StringBuffer retval = new StringBuffer(); - SheetReferences refs = book == null ? null : book.getSheetReferences(); - if (refs != null) { - String sheetName =refs.getSheetName((int)this.field_1_index_extern_sheet); - boolean appendQuotes = sheetName.indexOf(" ") >= 0; - if (appendQuotes) - retval.append("'"); - retval.append(sheetName); - if (appendQuotes) - retval.append("'"); - retval.append('!'); + String sheetName = getSheetName(book, field_1_index_extern_sheet); + if(sheetName != null) { + SheetNameFormatter.appendFormat(retval, sheetName); + retval.append( '!' ); } retval.append((new CellReference(getRow(),getColumn(),!isRowRelative(),!isColRelative())).toString()); return retval.toString(); diff --git a/src/java/org/apache/poi/hssf/record/formula/SheetNameFormatter.java b/src/java/org/apache/poi/hssf/record/formula/SheetNameFormatter.java new file mode 100755 index 0000000000..ba796db3b2 --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/formula/SheetNameFormatter.java @@ -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; itrue 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. + *

+ * This code is currently being used for translating formulas represented with Ptg + * 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: + *

    + *
  1. POI's own formula parser
  2. + *
  3. Visual reading by human
  4. + *
  5. VBA automation entry into Excel cell contents e.g. ActiveCell.Formula = "=c64!A1"
  6. + *
  7. Manual entry into Excel cell contents
  8. + *
  9. Some third party formula parser
  10. + *
+ * + * 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. + *

+ * 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: + *

+ *

+ * + * + * + * + *
Version  File Format  Last Column  Last Row
97-2003BIFF8"IV" (2^8)65536 (2^14)
2007BIFF12"XFD" (2^14)1048576 (2^20)
+ * POI currently targets BIFF8 (Excel 97-2003), so the following behaviour can be observed for + * this method: + *
+ * + * + * + * + * + * + * + * + * + * + * + *
Input           Result 
"A1", 1true
"a111", 1true
"A65536", 1true
"A65537", 1false
"iv1", 2true
"IW1", 2false
"AAA1", 3false
"a111", 1true
"Sheet1", 6false
+ */ + /* 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). + *

+ * Some notable cases: + *

+ * + * + * + * + * + * + * + * + * + * + *
Input Result Comments
"A1"  true 
"a111"  true 
"AA"  false 
"aa1"  true 
"A1A"  false 
"A1A1"  false 
"A$1:$C$20"  falseNot a plain cell reference
"SALES20080101"  trueStill needs delimiting even though well out of range
+ * + * @return true 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()); + } + +} diff --git a/src/java/org/apache/poi/hssf/record/formula/UnknownPtg.java b/src/java/org/apache/poi/hssf/record/formula/UnknownPtg.java index 9cc5158325..1badf51970 100644 --- a/src/java/org/apache/poi/hssf/record/formula/UnknownPtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/UnknownPtg.java @@ -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 */ diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java b/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java index 33417dad7f..19ead33f0e 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java @@ -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){ + + } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFDataFormat.java b/src/java/org/apache/poi/hssf/usermodel/HSSFDataFormat.java index 547ec83038..fb7ac49f39 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFDataFormat.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFDataFormat.java @@ -206,12 +206,12 @@ public class HSSFDataFormat implements DataFormat } /** - * get the format index that matches the given format string. - * Creates a new format if one is not found. Aliases text to the proper format. + * Get the format index that matches the given format + * string, creating a new format entry if required. + * Aliases text to the proper format as required. * @param format string matching a built in format * @return index of format. */ - public short getFormat( String format ) { ListIterator i; diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFHyperlink.java b/src/java/org/apache/poi/hssf/usermodel/HSSFHyperlink.java new file mode 100755 index 0000000000..e1bd28af6c --- /dev/null +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFHyperlink.java @@ -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"); + } +} diff --git a/src/java/org/apache/poi/util/CommonsLogger.java b/src/java/org/apache/poi/util/CommonsLogger.java index 4265a88af8..16a48f28f6 100644 --- a/src/java/org/apache/poi/util/CommonsLogger.java +++ b/src/java/org/apache/poi/util/CommonsLogger.java @@ -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); + } + } } diff --git a/src/java/org/apache/poi/util/POILogger.java b/src/java/org/apache/poi/util/POILogger.java index b2d358d4d8..514edf90ca 100644 --- a/src/java/org/apache/poi/util/POILogger.java +++ b/src/java/org/apache/poi/util/POILogger.java @@ -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); } /** diff --git a/src/java/org/apache/poi/util/SystemOutLogger.java b/src/java/org/apache/poi/util/SystemOutLogger.java index 8b3dc50d9a..af678e186a 100644 --- a/src/java/org/apache/poi/util/SystemOutLogger.java +++ b/src/java/org/apache/poi/util/SystemOutLogger.java @@ -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); + } + } } /** diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/Fill.java b/src/scratchpad/src/org/apache/poi/hslf/model/Fill.java index 7eae4edc4c..f9cc43a7ea 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/Fill.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/Fill.java @@ -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; } /** diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/Picture.java b/src/scratchpad/src/org/apache/poi/hslf/model/Picture.java index 0740e23bce..90efd5f3ee 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/Picture.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/Picture.java @@ -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(); } diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/Shape.java b/src/scratchpad/src/org/apache/poi/hslf/model/Shape.java index 56d77764e9..5cff81a8d7 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/Shape.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/Shape.java @@ -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; diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/Slide.java b/src/scratchpad/src/org/apache/poi/hslf/model/Slide.java index 201a069fc4..ea7201751d 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/Slide.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/Slide.java @@ -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(); + } } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Columns.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Columns.java index e25fad66e4..b75864e72d 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Columns.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Columns.java @@ -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); + } } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Counta.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Counta.java index 6c6b305742..9061e77e5d 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Counta.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Counta.java @@ -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 + * + * Syntax: COUNTIF ( range, criteria ) + * + * + * + *
range   is the range of cells to be counted based on the criteria
criteriais used to determine which cells to count
+ *

+ * + * @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; + } } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Index.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Index.java index db798ee0fa..aebf6aab0d 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Index.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Index.java @@ -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

+ * + * Syntax :
+ * INDEX ( reference, row_num[, column_num [, area_num]])
+ * INDEX ( array, row_num[, column_num]) + * + * + * + * + * + * + *
referencetypically an area reference, possibly a union of areas
arraya literal array value (currently not supported)
row_numselects the row within the array or area reference
column_numselects column within the array or area reference. default is 1
area_numused when reference is a union of areas
+ *

+ * + * @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; + } } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Rows.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Rows.java index 47e4dc8702..6a4eb8edb7 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Rows.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Rows.java @@ -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); + } } diff --git a/src/scratchpad/src/org/apache/poi/hwpf/usermodel/TableRow.java b/src/scratchpad/src/org/apache/poi/hwpf/usermodel/TableRow.java index ff9cf2b9c8..a88e32360e 100644 --- a/src/scratchpad/src/org/apache/poi/hwpf/usermodel/TableRow.java +++ b/src/scratchpad/src/org/apache/poi/hwpf/usermodel/TableRow.java @@ -58,7 +58,7 @@ public class TableRow p = getParagraph(end); s = p.text(); } - _cells[cellIndex] = new TableCell(start, end, this, levelNum, + _cells[cellIndex] = new TableCell(start, end+1, this, levelNum, _tprops.getRgtc()[cellIndex], _tprops.getRgdxaCenter()[cellIndex], _tprops.getRgdxaCenter()[cellIndex+1]-_tprops.getRgdxaCenter()[cellIndex]); diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/data/44296.ppt b/src/scratchpad/testcases/org/apache/poi/hslf/data/44296.ppt new file mode 100755 index 0000000000..1e0529db1d Binary files /dev/null and b/src/scratchpad/testcases/org/apache/poi/hslf/data/44296.ppt differ diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestBugs.java b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestBugs.java index 996a733ac9..f3f5f8e7ee 100644 --- a/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestBugs.java +++ b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestBugs.java @@ -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()); + } + } diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/data/44297.xls b/src/scratchpad/testcases/org/apache/poi/hssf/data/44297.xls new file mode 100755 index 0000000000..bc65efd4e0 Binary files /dev/null and b/src/scratchpad/testcases/org/apache/poi/hssf/data/44297.xls differ diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java new file mode 100755 index 0000000000..b5e0843671 --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java @@ -0,0 +1,44 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + + +package org.apache.poi.hssf.record.formula.functions; + +import junit.framework.Test; +import junit.framework.TestSuite; + +/** + * Direct tests for all implementors of Function. + * + * @author Josh Micich + */ +public final class AllIndividualFunctionEvaluationTests { + + // TODO - have this suite incorporated into a higher level one + public static Test suite() { + TestSuite result = new TestSuite("Tests for org.apache.poi.hssf.record.formula.functions"); + result.addTestSuite(TestCountFuncs.class); + result.addTestSuite(TestDate.class); + result.addTestSuite(TestFinanceLib.class); + result.addTestSuite(TestIndex.class); + result.addTestSuite(TestMathX.class); + result.addTestSuite(TestRowCol.class); + result.addTestSuite(TestStatsLib.class); + return result; + } + +} diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/EvalFactory.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/EvalFactory.java new file mode 100755 index 0000000000..958c486649 --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/EvalFactory.java @@ -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 Eval 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) + *

+ * nCols and nRows could have been derived + */ + public static AreaEval createAreaEval(String areaRefStr, int nCols, int nRows) { + int nValues = nCols * nRows; + ValueEval[] values = new ValueEval[nValues]; + for (int i = 0; i < nValues; i++) { + values[i] = ZERO; + } + + return new Area2DEval(new AreaPtg(areaRefStr), values); + } + + /** + * Creates a single RefEval (with value zero) + */ + public static RefEval createRefEval(String refStr) { + return new Ref2DEval(new ReferencePtg(refStr), ZERO, true); + } +} diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/NumericFunctionInvoker.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/NumericFunctionInvoker.java new file mode 100755 index 0000000000..87405a4918 --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/NumericFunctionInvoker.java @@ -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. + *

+ * Assumes that the cell coordinate parameters of + * Function.evaluate(args, srcCellRow, srcCellCol) + * are not required. + *

+ * This method cannot be used for confirming error return codes. Any non-numeric evaluation + * result causes the current junit test to fail. + */ + public static double invoke(Function f, Eval[] args) { + try { + return invokeInternal(f, args, -1, -1); + } catch (NumericEvalEx e) { + throw new AssertionFailedError("Evaluation of function (" + f.getClass().getName() + + ") failed: " + e.getMessage()); + } + + } + /** + * Formats nicer error messages for the junit output + */ + private static double invokeInternal(Function f, Eval[] args, int srcCellRow, int srcCellCol) + throws NumericEvalEx { + Eval evalResult = f.evaluate(args, srcCellRow, (short)srcCellCol); + if(evalResult == null) { + throw new NumericEvalEx("Result object was null"); + } + if(evalResult instanceof ErrorEval) { + ErrorEval ee = (ErrorEval) evalResult; + throw new NumericEvalEx(formatErrorMessage(ee)); + } + if(!(evalResult instanceof NumericValueEval)) { + throw new NumericEvalEx("Result object type (" + evalResult.getClass().getName() + + ") is invalid. Expected implementor of (" + + NumericValueEval.class.getName() + ")"); + } + + NumericValueEval result = (NumericValueEval) evalResult; + return result.getNumberValue(); + } + private static String formatErrorMessage(ErrorEval ee) { + if(errorCodesAreEqual(ee, ErrorEval.FUNCTION_NOT_IMPLEMENTED)) { + return "Function not implemented"; + } + if(errorCodesAreEqual(ee, ErrorEval.UNKNOWN_ERROR)) { + return "Unknown error"; + } + return "Error code=" + ee.getErrorCode(); + } + private static boolean errorCodesAreEqual(ErrorEval a, ErrorEval b) { + if(a==b) { + return true; + } + return a.getErrorCode() == b.getErrorCode(); + } + +} diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestCountFuncs.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestCountFuncs.java new file mode 100755 index 0000000000..fbaace9210 --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestCountFuncs.java @@ -0,0 +1,150 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + + +package org.apache.poi.hssf.record.formula.functions; + +import junit.framework.TestCase; + +import org.apache.poi.hssf.record.formula.AreaPtg; +import org.apache.poi.hssf.record.formula.ReferencePtg; +import org.apache.poi.hssf.record.formula.eval.Area2DEval; +import org.apache.poi.hssf.record.formula.eval.AreaEval; +import org.apache.poi.hssf.record.formula.eval.BlankEval; +import org.apache.poi.hssf.record.formula.eval.BoolEval; +import org.apache.poi.hssf.record.formula.eval.Eval; +import org.apache.poi.hssf.record.formula.eval.NumberEval; +import org.apache.poi.hssf.record.formula.eval.Ref2DEval; +import org.apache.poi.hssf.record.formula.eval.StringEval; +import org.apache.poi.hssf.record.formula.eval.ValueEval; + +/** + * Test cases for COUNT(), COUNTA() COUNTIF(), COUNTBLANK() + * + * @author Josh Micich + */ +public final class TestCountFuncs extends TestCase { + + public TestCountFuncs(String testName) { + super(testName); + } + + public void testCountA() { + + Eval[] args; + + args = new Eval[] { + new NumberEval(0), + }; + confirmCountA(1, args); + + args = new Eval[] { + new NumberEval(0), + new NumberEval(0), + new StringEval(""), + }; + confirmCountA(3, args); + + args = new Eval[] { + EvalFactory.createAreaEval("D2:F5", 3, 4), + }; + confirmCountA(12, args); + + args = new Eval[] { + EvalFactory.createAreaEval("D1:F5", 3, 5), // 15 + EvalFactory.createRefEval("A1"), + EvalFactory.createAreaEval("A1:F6", 7, 6), // 42 + new NumberEval(0), + }; + confirmCountA(59, args); + } + + public void testCountIf() { + + AreaEval range; + ValueEval[] values; + + // when criteria is a boolean value + values = new ValueEval[] { + new NumberEval(0), + new StringEval("TRUE"), // note - does not match boolean TRUE + BoolEval.TRUE, + BoolEval.FALSE, + BoolEval.TRUE, + BlankEval.INSTANCE, + }; + range = createAreaEval("A1:B2", values); + confirmCountIf(2, range, BoolEval.TRUE); + + // when criteria is numeric + values = new ValueEval[] { + new NumberEval(0), + new StringEval("2"), + new StringEval("2.001"), + new NumberEval(2), + new NumberEval(2), + BoolEval.TRUE, + BlankEval.INSTANCE, + }; + range = createAreaEval("A1:B2", values); + confirmCountIf(3, range, new NumberEval(2)); + // note - same results when criteria is a string that parses as the number with the same value + confirmCountIf(3, range, new StringEval("2.00")); + + if (false) { // not supported yet: + // when criteria is an expression (starting with a comparison operator) + confirmCountIf(4, range, new StringEval(">1")); + } + } + /** + * special case where the criteria argument is a cell reference + */ + public void testCountIfWithCriteriaReference() { + + ValueEval[] values = { + new NumberEval(22), + new NumberEval(25), + new NumberEval(21), + new NumberEval(25), + new NumberEval(25), + new NumberEval(25), + }; + Area2DEval arg0 = new Area2DEval(new AreaPtg("C1:C6"), values); + + Ref2DEval criteriaArg = new Ref2DEval(new ReferencePtg("A1"), new NumberEval(25), true); + Eval[] args= { arg0, criteriaArg, }; + + double actual = NumericFunctionInvoker.invoke(new Countif(), args); + assertEquals(4, actual, 0D); + } + + + private static AreaEval createAreaEval(String areaRefStr, ValueEval[] values) { + return new Area2DEval(new AreaPtg(areaRefStr), values); + } + + private static void confirmCountA(int expected, Eval[] args) { + double result = NumericFunctionInvoker.invoke(new Counta(), args); + assertEquals(expected, result, 0); + } + private static void confirmCountIf(int expected, AreaEval range, Eval criteria) { + + Eval[] args = { range, criteria, }; + double result = NumericFunctionInvoker.invoke(new Countif(), args); + assertEquals(expected, result, 0); + } +} diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestIndex.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestIndex.java new file mode 100755 index 0000000000..902c4122ef --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestIndex.java @@ -0,0 +1,89 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + + +package org.apache.poi.hssf.record.formula.functions; + +import junit.framework.TestCase; + +import org.apache.poi.hssf.record.formula.AreaPtg; +import org.apache.poi.hssf.record.formula.eval.Area2DEval; +import org.apache.poi.hssf.record.formula.eval.Eval; +import org.apache.poi.hssf.record.formula.eval.NumberEval; +import org.apache.poi.hssf.record.formula.eval.ValueEval; + +/** + * Tests for the INDEX() function + * + * @author Josh Micich + */ +public final class TestIndex extends TestCase { + + public TestIndex(String testName) { + super(testName); + } + + private static final double[] TEST_VALUES0 = { + 1, 2, + 3, 4, + 5, 6, + 7, 8, + 9, 10, + 11, 12, + 13, // excess array element. TODO - Area2DEval currently has no validation to ensure correct size of values array + }; + + /** + * For the case when the first argument to INDEX() is an area reference + */ + public void testEvaluateAreaReference() { + + double[] values = TEST_VALUES0; + confirmAreaEval("C1:D6", values, 4, 1, 7); + confirmAreaEval("C1:D6", values, 6, 2, 12); + confirmAreaEval("C1:D6", values, 3, -1, 5); + + // now treat same data as 3 columns, 4 rows + confirmAreaEval("C10:E13", values, 2, 2, 5); + confirmAreaEval("C10:E13", values, 4, -1, 10); + } + + /** + * @param areaRefString in Excel notation e.g. 'D2:E97' + * @param dValues array of evaluated values for the area reference + * @param rowNum 1-based + * @param colNum 1-based, pass -1 to signify argument not present + */ + private static void confirmAreaEval(String areaRefString, double[] dValues, + int rowNum, int colNum, double expectedResult) { + ValueEval[] values = new ValueEval[dValues.length]; + for (int i = 0; i < values.length; i++) { + values[i] = new NumberEval(dValues[i]); + } + Area2DEval arg0 = new Area2DEval(new AreaPtg(areaRefString), values); + + Eval[] args; + if (colNum > 0) { + args = new Eval[] { arg0, new NumberEval(rowNum), new NumberEval(colNum), }; + } else { + args = new Eval[] { arg0, new NumberEval(rowNum), }; + } + + double actual = NumericFunctionInvoker.invoke(new Index(), args); + assertEquals(expectedResult, actual, 0D); + } +} diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestRowCol.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestRowCol.java new file mode 100755 index 0000000000..4002c30d0f --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestRowCol.java @@ -0,0 +1,102 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + + +package org.apache.poi.hssf.record.formula.functions; + +import junit.framework.TestCase; + +import org.apache.poi.hssf.record.formula.eval.Eval; + +/** + * Tests for ROW(), ROWS(), COLUMN(), COLUMNS() + * + * @author Josh Micich + */ +public final class TestRowCol extends TestCase { + + public TestRowCol(String testName) { + super(testName); + } + + public void testCol() { + Function target = new Column(); + { + Eval[] args = { EvalFactory.createRefEval("C5"), }; + double actual = NumericFunctionInvoker.invoke(target, args); + assertEquals(3, actual, 0D); + } + { + Eval[] args = { EvalFactory.createAreaEval("E2:H12", 4, 11), }; + double actual = NumericFunctionInvoker.invoke(target, args); + assertEquals(5, actual, 0D); + } + } + + public void testRow() { + Function target = new Row(); + { + Eval[] args = { EvalFactory.createRefEval("C5"), }; + double actual = NumericFunctionInvoker.invoke(target, args); + assertEquals(5, actual, 0D); + } + { + Eval[] args = { EvalFactory.createAreaEval("E2:H12", 4, 11), }; + double actual = NumericFunctionInvoker.invoke(target, args); + assertEquals(2, actual, 0D); + } + } + + public void testColumns() { + + confirmColumnsFunc("A1:F1", 6, 1); + confirmColumnsFunc("A1:C2", 3, 2); + confirmColumnsFunc("A1:B3", 2, 3); + confirmColumnsFunc("A1:A6", 1, 6); + + Eval[] args = { EvalFactory.createRefEval("C5"), }; + double actual = NumericFunctionInvoker.invoke(new Columns(), args); + assertEquals(1, actual, 0D); + } + + public void testRows() { + + confirmRowsFunc("A1:F1", 6, 1); + confirmRowsFunc("A1:C2", 3, 2); + confirmRowsFunc("A1:B3", 2, 3); + confirmRowsFunc("A1:A6", 1, 6); + + Eval[] args = { EvalFactory.createRefEval("C5"), }; + double actual = NumericFunctionInvoker.invoke(new Rows(), args); + assertEquals(1, actual, 0D); + } + + private static void confirmRowsFunc(String areaRefStr, int nCols, int nRows) { + Eval[] args = { EvalFactory.createAreaEval(areaRefStr, nCols, nRows), }; + + double actual = NumericFunctionInvoker.invoke(new Rows(), args); + assertEquals(nRows, actual, 0D); + } + + + private static void confirmColumnsFunc(String areaRefStr, int nCols, int nRows) { + Eval[] args = { EvalFactory.createAreaEval(areaRefStr, nCols, nRows), }; + + double actual = NumericFunctionInvoker.invoke(new Columns(), args); + assertEquals(nCols, actual, 0D); + } +} diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestBug44297.java b/src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestBug44297.java new file mode 100755 index 0000000000..ce4afd36f6 --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestBug44297.java @@ -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); + } + +} diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestFormulaEvaluatorDocs.java b/src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestFormulaEvaluatorDocs.java index cd2acc7ea9..6c2e3b6412 100644 --- a/src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestFormulaEvaluatorDocs.java +++ b/src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestFormulaEvaluatorDocs.java @@ -82,7 +82,7 @@ public class TestFormulaEvaluatorDocs extends TestCase { assertEquals(HSSFCell.CELL_TYPE_FORMULA, wb.getSheetAt(0).getRow(1).getCell((short)2).getCellType()); assertEquals(22.3, wb.getSheetAt(1).getRow(0).getCell((short)0).getNumericCellValue(), 0); - assertEquals("S1!A1", wb.getSheetAt(1).getRow(0).getCell((short)0).getCellFormula()); + assertEquals("'S1'!A1", wb.getSheetAt(1).getRow(0).getCell((short)0).getCellFormula()); assertEquals(HSSFCell.CELL_TYPE_FORMULA, wb.getSheetAt(1).getRow(0).getCell((short)0).getCellType()); diff --git a/src/scratchpad/testcases/org/apache/poi/hwpf/data/Bug44292.doc b/src/scratchpad/testcases/org/apache/poi/hwpf/data/Bug44292.doc new file mode 100644 index 0000000000..fd7ca6cc3e Binary files /dev/null and b/src/scratchpad/testcases/org/apache/poi/hwpf/data/Bug44292.doc differ diff --git a/src/scratchpad/testcases/org/apache/poi/hwpf/usermodel/TestProblems.java b/src/scratchpad/testcases/org/apache/poi/hwpf/usermodel/TestProblems.java index 8e7f47ed96..e82c4d1304 100644 --- a/src/scratchpad/testcases/org/apache/poi/hwpf/usermodel/TestProblems.java +++ b/src/scratchpad/testcases/org/apache/poi/hwpf/usermodel/TestProblems.java @@ -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()); + } } diff --git a/src/testcases/org/apache/poi/hssf/HSSFTests.java b/src/testcases/org/apache/poi/hssf/HSSFTests.java index 1bc9df1798..1e0edd6825 100644 --- a/src/testcases/org/apache/poi/hssf/HSSFTests.java +++ b/src/testcases/org/apache/poi/hssf/HSSFTests.java @@ -75,13 +75,7 @@ import org.apache.poi.hssf.record.TestUnitsRecord; import org.apache.poi.hssf.record.TestValueRangeRecord; import org.apache.poi.hssf.record.aggregates.TestRowRecordsAggregate; import org.apache.poi.hssf.record.aggregates.TestValueRecordsAggregate; -import org.apache.poi.hssf.record.formula.TestAreaErrPtg; -import org.apache.poi.hssf.record.formula.TestErrPtg; -import org.apache.poi.hssf.record.formula.TestFuncPtg; -import org.apache.poi.hssf.record.formula.TestIntersectionPtg; -import org.apache.poi.hssf.record.formula.TestPercentPtg; -import org.apache.poi.hssf.record.formula.TestRangePtg; -import org.apache.poi.hssf.record.formula.TestUnionPtg; +import org.apache.poi.hssf.record.formula.AllFormulaTests; import org.apache.poi.hssf.usermodel.TestBugs; import org.apache.poi.hssf.usermodel.TestCellStyle; import org.apache.poi.hssf.usermodel.TestCloneSheet; @@ -215,13 +209,7 @@ public class HSSFTests suite.addTest(new TestSuite(TestSheetReferences.class)); - suite.addTest(new TestSuite(TestAreaErrPtg.class)); - suite.addTest(new TestSuite(TestErrPtg.class)); - suite.addTest(new TestSuite(TestFuncPtg.class)); - suite.addTest(new TestSuite(TestIntersectionPtg.class)); - suite.addTest(new TestSuite(TestPercentPtg.class)); - suite.addTest(new TestSuite(TestRangePtg.class)); - suite.addTest(new TestSuite(TestUnionPtg.class)); + suite.addTest(AllFormulaTests.suite()); suite.addTest(new TestSuite(TestValueRecordsAggregate.class)); suite.addTest(new TestSuite(TestNameRecord.class)); suite.addTest(new TestSuite(TestEventRecordFactory.class)); diff --git a/src/testcases/org/apache/poi/hssf/data/HyperlinksOnManySheets.xls b/src/testcases/org/apache/poi/hssf/data/HyperlinksOnManySheets.xls new file mode 100755 index 0000000000..1884e57c5b Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/HyperlinksOnManySheets.xls differ diff --git a/src/testcases/org/apache/poi/hssf/data/SimpleWithChoose.xls b/src/testcases/org/apache/poi/hssf/data/SimpleWithChoose.xls new file mode 100755 index 0000000000..96a8e743a8 Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/SimpleWithChoose.xls differ diff --git a/src/testcases/org/apache/poi/hssf/data/TestDataValidation.xls b/src/testcases/org/apache/poi/hssf/data/TestDataValidation.xls index a9460375cb..0b2a869486 100644 Binary files a/src/testcases/org/apache/poi/hssf/data/TestDataValidation.xls and b/src/testcases/org/apache/poi/hssf/data/TestDataValidation.xls differ diff --git a/src/testcases/org/apache/poi/hssf/data/WithHyperlink.xls b/src/testcases/org/apache/poi/hssf/data/WithHyperlink.xls new file mode 100644 index 0000000000..e136506c20 Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/WithHyperlink.xls differ diff --git a/src/testcases/org/apache/poi/hssf/data/WithTwoHyperLinks.xls b/src/testcases/org/apache/poi/hssf/data/WithTwoHyperLinks.xls new file mode 100644 index 0000000000..6ee60b535f Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/WithTwoHyperLinks.xls differ diff --git a/src/testcases/org/apache/poi/hssf/eventusermodel/TestHSSFEventFactory.java b/src/testcases/org/apache/poi/hssf/eventusermodel/TestHSSFEventFactory.java index bd936a0afc..049b43ef93 100644 --- a/src/testcases/org/apache/poi/hssf/eventusermodel/TestHSSFEventFactory.java +++ b/src/testcases/org/apache/poi/hssf/eventusermodel/TestHSSFEventFactory.java @@ -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