<property name="mavendist.poi.dir" location="build/maven-dist/poi"/>
<property name="mavendist.oap.dir" location="build/maven-dist/org.apache.poi"/>
<property name="jar.name" value="poi"/>
- <property name="version.id" value="3.0.2-beta1"/>
+ <property name="version.id" value="3.1-alpha1"/>
<property name="halt.on.test.failure" value="true"/>
<property name="jdk.version.source" value="1.5"
<antcall target="with.clover"/>
<mkdir dir="build"/>
+ <mkdir dir="build/non-ant-classes"/>
<mkdir dir="${main.output.dir}"/>
<mkdir dir="${main14.output.dir}"/>
<mkdir dir="${scratchpad.output.dir}"/>
</devs>
<!-- Don't forget to update status.xml too! -->
- <release version="3.0.2-FINAL" date="2008-??-??">
+ <release version="3.1-beta1" date="2008-??-??">
+ <action dev="POI-DEVELOPERS" type="fix">44345 - Implement CountA, CountIf, Index, Rows and Columns functions</action>
+ <action dev="POI-DEVELOPERS" type="fix">44336 - Properly escape sheet names as required when figuring out the text of formulas</action>
+ <action dev="POI-DEVELOPERS" type="add">44326 - Improvements to how SystemOutLogger and CommonsLogger log messages with exceptions, and avoid an infinite loop with certain log messages with exceptions</action>
+ <action dev="POI-DEVELOPERS" type="add">Support for a completed Record based "pull" stream, via org.apache.poi.hssf.eventusermodel.HSSFRecordStream, to complement the existing "push" Event User Model listener stuff</action>
+ </release>
+ <release version="3.0.2-FINAL" date="2008-02-04">
+ <action dev="POI-DEVELOPERS" type="fix">44297 - IntPtg must operate with unsigned short. Reading signed short results in incorrect formula calculation</action>
+ <action dev="POI-DEVELOPERS" type="fix">44296 - Fix for reading slide background images</action>
+ <action dev="POI-DEVELOPERS" type="fix">44293 - Avoid swapping AreaPtgs from relative to absolute</action>
+ <action dev="POI-DEVELOPERS" type="fix">44292 - Correctly process the last paragraph in a word file</action>
<action dev="POI-DEVELOPERS" type="fix">44254 - Avoid some unread byte warnings, and properly understand DVALRecord</action>
<action dev="POI-DEVELOPERS" type="add">Add another formula evaluation method, evaluateFormulaCell(cell), which will re-calculate the value for a formula, without affecting the formula itself.</action>
<action dev="POI-DEVELOPERS" type="fix">41726 - Fix how we handle signed cell offsets in relative areas and references</action>
<!-- Don't forget to update changes.xml too! -->
<changes>
- <release version="3.0.2-FINAL" date="2008-??-??">
+ <release version="3.1-beta1" date="2008-??-??">
+ <action dev="POI-DEVELOPERS" type="fix">44345 - Implement CountA, CountIf, Index, Rows and Columns functions</action>
+ <action dev="POI-DEVELOPERS" type="fix">44336 - Properly escape sheet names as required when figuring out the text of formulas</action>
+ <action dev="POI-DEVELOPERS" type="add">44326 - Improvements to how SystemOutLogger and CommonsLogger log messages with exceptions, and avoid an infinite loop with certain log messages with exceptions</action>
+ <action dev="POI-DEVELOPERS" type="add">Support for a completed Record based "pull" stream, via org.apache.poi.hssf.eventusermodel.HSSFRecordStream, to complement the existing "push" Event User Model listener stuff</action>
+ </release>
+ <release version="3.0.2-FINAL" date="2008-02-04">
+ <action dev="POI-DEVELOPERS" type="fix">44297 - IntPtg must operate with unsigned short. Reading signed short results in incorrect formula calculation</action>
+ <action dev="POI-DEVELOPERS" type="fix">44296 - Fix for reading slide background images</action>
+ <action dev="POI-DEVELOPERS" type="fix">44293 - Avoid swapping AreaPtgs from relative to absolute</action>
+ <action dev="POI-DEVELOPERS" type="fix">44292 - Correctly process the last paragraph in a word file</action>
<action dev="POI-DEVELOPERS" type="fix">44254 - Avoid some unread byte warnings, and properly understand DVALRecord</action>
<action dev="POI-DEVELOPERS" type="add">Add another formula evaluation method, evaluateFormulaCell(cell), which will re-calculate the value for a formula, without affecting the formula itself.</action>
<action dev="POI-DEVELOPERS" type="fix">41726 - Fix how we handle signed cell offsets in relative areas and references</action>
case FileSharingRecord.sid:
retval = new FileSharingRecord( in );
break;
+ case HyperlinkRecord.sid:
+ retval = new HyperlinkRecord( in );
+ break;
default:
retval = new UnknownRecord( in );
}
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;
}
}
--- /dev/null
+/* ====================================================================
+ 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
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
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) {
// 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);
return null;
}
+ public List getHyperlinks()
+ {
+ return hyperlinks;
+ }
+
public List getRecords()
{
return records.getRecords();
--- /dev/null
+/* ====================================================================
+ Copyright 2002-2004 Apache Software Foundation
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ ==================================================================== */
+
+package org.apache.poi.hssf.record;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.StringUtil;
+
+/**
+ * The <code>HyperlinkRecord</code> wraps an HLINK-record
+ * from the Excel-97 format.
+ * Supports only external links for now (eg http://)
+ *
+ * @author Mark Hissink Muller <a href="mailto:mark@hissinkmuller.nl >mark&064;hissinkmuller.nl</a>
+ */
+public class HyperlinkRecord extends Record implements CellValueRecordInterface
+{
+ /** Indicates the URL in the Record */
+ private static byte[] GUID_OF_URL_MONIKER =
+ { -32, -55, -22, 121, -7, -70, -50, 17, -116, -126, 0, -86, 0, 75, -87, 11 };
+
+ /** Indicates the STD_LINK in the Record */
+ // MHM: to be added when necessary
+ private static byte[] GUID_OF_STD_LINK = {};
+
+ /** Logger */
+ public static final Log log = LogFactory.getLog(HyperlinkRecord.class);
+
+ // quick and dirty
+ private static final boolean _DEBUG_ = true;
+
+ public final static short sid = 0x1b8;
+
+ private short field_1_unknown;
+ private int field_2_row;
+ private short field_3_column;
+ private short field_4_xf_index;
+ private byte[] field_5_unknown;
+ private int field_6_label_opts;
+ private int field_7_url_len;
+ private int field_8_label_len;
+ private String field_9_label;
+ private byte[] field_10_unknown;
+ private int field_11_url_opts;
+ private String field_12_url;
+
+ /** Blank Constructor */
+ public HyperlinkRecord()
+ {
+ }
+
+ /** Real Constructor */
+ public HyperlinkRecord(RecordInputStream in)
+ {
+ super(in);
+ }
+
+ /* (non-Javadoc)
+ * @see org.apache.poi.hssf.record.CellValueRecordInterface#getColumn()
+ */
+ public short getColumn()
+ {
+ return field_3_column;
+ }
+
+ /* (non-Javadoc)
+ * @see org.apache.poi.hssf.record.CellValueRecordInterface#getRow()
+ */
+ public int getRow()
+ {
+ return field_2_row;
+ }
+
+ /* (non-Javadoc)
+ * @see org.apache.poi.hssf.record.CellValueRecordInterface#getXFIndex()
+ */
+ public short getXFIndex()
+ {
+ return field_4_xf_index;
+ }
+
+ /* (non-Javadoc)
+ * @see org.apache.poi.hssf.record.CellValueRecordInterface#isAfter(org.apache.poi.hssf.record.CellValueRecordInterface)
+ */
+ public boolean isAfter(CellValueRecordInterface i)
+ {
+ if (this.getRow() < i.getRow())
+ {
+ return false;
+ }
+ if ((this.getRow() == i.getRow()) && (this.getColumn() < i.getColumn()))
+ {
+ return false;
+ }
+ if ((this.getRow() == i.getRow()) && (this.getColumn() == i.getColumn()))
+ {
+ return false;
+ }
+ return true;
+ }
+
+ /* (non-Javadoc)
+ * @see org.apache.poi.hssf.record.CellValueRecordInterface#isBefore(org.apache.poi.hssf.record.CellValueRecordInterface)
+ */
+ public boolean isBefore(CellValueRecordInterface i)
+ {
+ if (this.getRow() > i.getRow())
+ {
+ return false;
+ }
+ if ((this.getRow() == i.getRow()) && (this.getColumn() > i.getColumn()))
+ {
+ return false;
+ }
+ if ((this.getRow() == i.getRow()) && (this.getColumn() == i.getColumn()))
+ {
+ return false;
+ }
+ return true;
+ }
+
+ /* (non-Javadoc)
+ * @see org.apache.poi.hssf.record.CellValueRecordInterface#isEqual(org.apache.poi.hssf.record.CellValueRecordInterface)
+ */
+ public boolean isEqual(CellValueRecordInterface i)
+ {
+ return ((this.getRow() == i.getRow()) && (this.getColumn() == i.getColumn()));
+ }
+
+ /* (non-Javadoc)
+ * @see org.apache.poi.hssf.record.CellValueRecordInterface#setColumn(short)
+ */
+ public void setColumn(short col)
+ {
+ this.field_3_column = col;
+
+ }
+
+ /* (non-Javadoc)
+ * @see org.apache.poi.hssf.record.CellValueRecordInterface#setRow(int)
+ */
+ public void setRow(int row)
+ {
+ this.field_2_row = row;
+
+ }
+
+ /* (non-Javadoc)
+ * @see org.apache.poi.hssf.record.CellValueRecordInterface#setXFIndex(short)
+ */
+ public void setXFIndex(short xf)
+ {
+ this.field_4_xf_index = xf;
+
+ }
+
+ /**
+ * @param in the RecordInputstream to read the record from
+ */
+ protected void fillFields(RecordInputStream in)
+ {
+// System.err.println(in.currentSid);
+// System.err.println(in.currentLength);
+// for(int i=0; i<300; i++) {
+// System.err.println(in.readByte());
+// }
+// if(1==1)
+// throw new IllegalArgumentException("");
+
+ field_1_unknown = in.readShort();
+ field_2_row = in.readUShort();
+ field_3_column = in.readShort();
+ field_4_xf_index = in.readShort();
+
+ // Next up is 16 bytes we don't get
+ field_5_unknown = new byte[16];
+ try {
+ in.read(field_5_unknown);
+ } catch(IOException e) { throw new IllegalStateException(e.getMessage()); }
+
+ // Some sort of opts
+ field_6_label_opts = in.readInt();
+
+ // Now for lengths, in characters
+ field_7_url_len = in.readInt();
+ field_8_label_len = in.readInt();
+
+ // Now we have the label, as little endian unicode,
+ // with a trailing \0
+ field_9_label = in.readUnicodeLEString(field_8_label_len);
+
+ // Next up is some more data we can't make sense of
+ field_10_unknown = new byte[16];
+ try {
+ in.read(field_10_unknown);
+ } catch(IOException e) { throw new IllegalStateException(e.getMessage()); }
+
+ // Might need to nudge the length by one byte
+ // This is an empirical hack!
+ field_11_url_opts = in.readInt();
+ if(field_11_url_opts == 44) {
+ field_7_url_len--;
+ }
+
+ // Finally it's the URL
+ int strlen = field_7_url_len > (in.remaining()/2) ? (in.remaining()/2) : field_7_url_len;
+ field_12_url = in.readUnicodeLEString(strlen);
+ }
+
+ /* (non-Javadoc)
+ * @see org.apache.poi.hssf.record.Record#getSid()
+ */
+ public short getSid()
+ {
+ return HyperlinkRecord.sid;
+ }
+
+ protected void validateSid(short id)
+ {
+ if (id != sid)
+ {
+ throw new RecordFormatException("NOT A HYPERLINKRECORD!");
+ }
+ }
+
+ public int serialize(int offset, byte[] data)
+ {
+ LittleEndian.putShort(data, 0 + offset, sid);
+ LittleEndian.putShort(data, 2 + offset,
+ ( short )(getRecordSize()-4));
+ LittleEndian.putShort(data, 4 + offset, field_1_unknown);
+ LittleEndian.putUShort(data, 6 + offset, field_2_row);
+ LittleEndian.putShort(data, 8 + offset, field_3_column);
+ LittleEndian.putShort(data, 10 + offset, field_4_xf_index);
+
+ offset += 12;
+ for(int i=0; i<field_5_unknown.length; i++) {
+ data[offset] = field_5_unknown[i];
+ offset++;
+ }
+
+ LittleEndian.putInt(data, offset, field_6_label_opts);
+ offset += 4;
+ LittleEndian.putInt(data, offset, field_7_url_len);
+ offset += 4;
+ LittleEndian.putInt(data, offset, field_8_label_len);
+ offset += 4;
+ StringUtil.putUnicodeLE(field_9_label, data, offset);
+ offset += field_9_label.length()*2;
+
+ for(int i=0; i<field_10_unknown.length; i++) {
+ data[offset] = field_10_unknown[i];
+ offset++;
+ }
+
+ LittleEndian.putInt(data, offset, field_11_url_opts);
+ offset += 4;
+ StringUtil.putUnicodeLE(field_12_url, data, offset);
+
+ return getRecordSize();
+ }
+
+ public int getRecordSize()
+ {
+ // We have:
+ // 4 shorts
+ // junk
+ // 3 ints
+ // label
+ // junk
+ // int
+ // url
+ return 4 + 4*2 + field_5_unknown.length +
+ 3*4 + field_9_label.length()*2 +
+ field_10_unknown.length + 4 +
+ field_12_url.length()*2;
+ }
+
+ public String toString()
+ {
+ StringBuffer buffer = new StringBuffer();
+
+ buffer.append("[HYPERLINK RECORD]\n");
+ buffer.append(" .row = ").append(Integer.toHexString(getRow())).append("\n");
+ buffer.append(" .column = ").append(Integer.toHexString(getColumn())).append("\n");
+ buffer.append(" .xfindex = ").append(Integer.toHexString(getXFIndex())).append("\n");
+ buffer.append(" .label = ").append(field_9_label).append("\n");
+ buffer.append(" .url = ").append(field_12_url).append("\n");
+ buffer.append("[/HYPERLINK RECORD]\n");
+ return buffer.toString();
+ }
+
+ /**
+ * @return Returns the label.
+ */
+ public String getLabel()
+ {
+ if(field_9_label.length() == 0) {
+ return "";
+ } else {
+ // Trim off \0
+ return field_9_label.substring(0, field_9_label.length() - 1);
+ }
+ }
+
+ /**
+ * @param label The label to set.
+ */
+ public void setLabel(String label)
+ {
+ this.field_9_label = label + '\u0000';
+ this.field_8_label_len = field_9_label.length();
+ }
+
+ /**
+ * @return Returns the Url.
+ */
+ public URL getUrl() throws MalformedURLException
+ {
+ return new URL(getUrlString());
+ }
+ public String getUrlString()
+ {
+ if(field_12_url.length() == 0) {
+ return "";
+ } else {
+ // Trim off \0
+ return field_12_url.substring(0, field_12_url.length() - 1);
+ }
+ }
+
+ /**
+ * @param url The url to set.
+ */
+ public void setUrl(URL url)
+ {
+ setUrl(url.toString());
+ }
+ /**
+ * @param url The url to set.
+ */
+ public void setUrl(String url)
+ {
+ this.field_12_url = url + '\u0000';
+ this.field_7_url_len = field_12_url.length();
+ }
+
+ public int getOptions(){
+ return field_11_url_opts;
+ }
+}
WriteProtectRecord.class, FilePassRecord.class, PaneRecord.class,
NoteRecord.class, ObjectProtectRecord.class, ScenarioProtectRecord.class,
FileSharingRecord.class, ChartTitleFormatRecord.class,
- DVRecord.class, DVALRecord.class, UncalcedRecord.class
+ DVRecord.class, DVALRecord.class, UncalcedRecord.class,
+ HyperlinkRecord.class
};
}
private static Map recordsMap = recordsToMap(records);
*/
public String readUnicodeLEString(int length) {
if ((length < 0) || (((remaining() / 2) < length) && !isContinueNext())) {
- throw new IllegalArgumentException("Illegal length");
+ throw new IllegalArgumentException("Illegal length - asked for " + length + " but only " + (remaining()/2) + " left!");
}
StringBuffer buf = new StringBuffer(length);
setLastRowRelative( !lastCell.isRowAbsolute() );
}
+ /**
+ * @return text representation of this area reference that can be used in text
+ * formulas. The sheet name will get properly delimited if required.
+ */
public String toFormulaString(Workbook book)
{
- SheetReferences refs = book == null ? null : book.getSheetReferences();
StringBuffer retval = new StringBuffer();
- if ( refs != null )
- {
- retval.append( refs.getSheetName( this.field_1_index_extern_sheet ) );
+ String sheetName = Ref3DPtg.getSheetName(book, field_1_index_extern_sheet);
+ if(sheetName != null) {
+ SheetNameFormatter.appendFormat(retval, sheetName);
retval.append( '!' );
}
retval.append( ( new CellReference( getFirstRow(), getFirstColumn(), !isFirstRowRelative(), !isFirstColRelative() ) ).toString() );
private short field_3_first_column;
private short field_4_last_column;
- private BitField rowRelative = BitFieldFactory.getInstance(0x8000);
- private BitField colRelative = BitFieldFactory.getInstance(0x4000);
- private BitField column = BitFieldFactory.getInstance(0x3FFF);
+ private final static BitField rowRelative = BitFieldFactory.getInstance(0x8000);
+ private final static BitField colRelative = BitFieldFactory.getInstance(0x4000);
+ private final static BitField columnMask = BitFieldFactory.getInstance(0x3FFF);
protected AreaPtg() {
//Required for clone methods
*/
public short getFirstColumn()
{
- return column.getShortValue(field_3_first_column);
+ return columnMask.getShortValue(field_3_first_column);
}
/**
*/
public void setFirstColumn(short column)
{
- field_3_first_column = column; // fixme
+ field_3_first_column=columnMask.setShortValue(field_3_first_column, column);
}
/**
*/
public short getLastColumn()
{
- return column.getShortValue(field_4_last_column);
+ return columnMask.getShortValue(field_4_last_column);
}
/**
*/
public void setLastColumn(short column)
{
- field_4_last_column = column; // fixme
+ field_4_last_column=columnMask.setShortValue(field_4_last_column, column);
}
/**
public final static byte sid = 0x08;
private final static String CONCAT = "&";
-
+
public ConcatPtg(RecordInputStream in)
{
-
- // doesn't need anything
+ // No contents
}
public ConcatPtg() {
{
public final static int SIZE = 3;
public final static byte sid = 0x1e;
- private short field_1_value;
+ private int field_1_value;
private IntPtg() {
//Required for clone methods
public IntPtg(RecordInputStream in)
{
- setValue(in.readShort());
+ setValue(in.readUShort());
}
// IntPtg should be able to create itself, shouldnt have to call setValue
public IntPtg(String formulaToken) {
- setValue(Short.parseShort(formulaToken));
+ setValue(Integer.parseInt(formulaToken));
}
/**
* Sets the wrapped value.
* Normally you should call with a positive int.
*/
- public void setValue(short value)
- {
- field_1_value = value;
- }
-
- /**
- * Sets the unsigned value.
- * (Handles conversion to the internal short value)
- */
public void setValue(int value)
{
- if(value > Short.MAX_VALUE) {
- // Need to wrap
- value -= (Short.MAX_VALUE+1)*2;
- }
- field_1_value = (short)value;
+ if(value < 0 || value > (Short.MAX_VALUE + 1)*2 )
+ throw new IllegalArgumentException("Unsigned short is out of range: " + value);
+ field_1_value = value;
}
/**
* Returns the value as a short, which may have
* been wrapped into negative numbers
*/
- public short getValue()
+ public int getValue()
{
return field_1_value;
}
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()
break;
case DividePtg.sid : // 0x06
- retval = new DividePtg(in);
- break;
+ retval = new DividePtg(in);
+ break;
case PowerPtg.sid : // 0x07
retval = new PowerPtg(in);
break;
case AttrPtg.sid : // 0x19
+ case 0x1a :
retval = new AttrPtg(in);
break;
break;
case NumberPtg.sid : // 0x1f
- retval = new NumberPtg(in);
- break;
+ retval = new NumberPtg(in);
+ break;
case ArrayPtg.sid : // 0x20
retval = new ArrayPtg(in);
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 + ")");
*/
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 )
}
- 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();
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+
+package org.apache.poi.hssf.record.formula;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Formats sheet names for use in formula expressions.
+ *
+ * @author Josh Micich
+ */
+final class SheetNameFormatter {
+
+ private static final String BIFF8_LAST_COLUMN = "IV";
+ private static final int BIFF8_LAST_COLUMN_TEXT_LEN = BIFF8_LAST_COLUMN.length();
+ private static final String BIFF8_LAST_ROW = String.valueOf(0x10000);
+ private static final int BIFF8_LAST_ROW_TEXT_LEN = BIFF8_LAST_ROW.length();
+
+ private static final char DELIMITER = '\'';
+
+ private static final Pattern CELL_REF_PATTERN = Pattern.compile("([A-Za-z])+[0-9]+");
+
+ private SheetNameFormatter() {
+ // no instances of this class
+ }
+ /**
+ * Used to format sheet names as they would appear in cell formula expressions.
+ * @return the sheet name unchanged if there is no need for delimiting. Otherwise the sheet
+ * name is enclosed in single quotes ('). Any single quotes which were already present in the
+ * sheet name will be converted to double single quotes ('').
+ */
+ public static String format(String rawSheetName) {
+ StringBuffer sb = new StringBuffer(rawSheetName.length() + 2);
+ appendFormat(sb, rawSheetName);
+ return sb.toString();
+ }
+
+ /**
+ * Convenience method for when a StringBuffer is already available
+ *
+ * @param out - sheet name will be appended here possibly with delimiting quotes
+ */
+ public static void appendFormat(StringBuffer out, String rawSheetName) {
+ boolean needsQuotes = needsDelimiting(rawSheetName);
+ if(needsQuotes) {
+ out.append(DELIMITER);
+ appendAndEscape(out, rawSheetName);
+ out.append(DELIMITER);
+ } else {
+ out.append(rawSheetName);
+ }
+ }
+
+ private static void appendAndEscape(StringBuffer sb, String rawSheetName) {
+ int len = rawSheetName.length();
+ for(int i=0; i<len; i++) {
+ char ch = rawSheetName.charAt(i);
+ if(ch == DELIMITER) {
+ // single quotes (') are encoded as ('')
+ sb.append(DELIMITER);
+ }
+ sb.append(ch);
+ }
+ }
+
+ private static boolean needsDelimiting(String rawSheetName) {
+ int len = rawSheetName.length();
+ if(len < 1) {
+ throw new RuntimeException("Zero length string is an invalid sheet name");
+ }
+ if(Character.isDigit(rawSheetName.charAt(0))) {
+ // sheet name with digit in the first position always requires delimiting
+ return true;
+ }
+ for(int i=0; i<len; i++) {
+ char ch = rawSheetName.charAt(i);
+ if(isSpecialChar(ch)) {
+ return true;
+ }
+ }
+ if(Character.isLetter(rawSheetName.charAt(0))
+ && Character.isDigit(rawSheetName.charAt(len-1))) {
+ // note - values like "A$1:$C$20" don't get this far
+ if(nameLooksLikePlainCellReference(rawSheetName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @return <code>true</code> if the presence of the specified character in a sheet name would
+ * require the sheet name to be delimited in formulas. This includes every non-alphanumeric
+ * character besides underscore '_'.
+ */
+ /* package */ static boolean isSpecialChar(char ch) {
+ // note - Character.isJavaIdentifierPart() would allow dollars '$'
+ if(Character.isLetterOrDigit(ch)) {
+ return false;
+ }
+ switch(ch) {
+ case '_': // underscore is ok
+ return false;
+ case '\n':
+ case '\r':
+ case '\t':
+ throw new RuntimeException("Illegal character (0x"
+ + Integer.toHexString(ch) + ") found in sheet name");
+ }
+ return true;
+ }
+
+
+ /**
+ * Used to decide whether sheet names like 'AB123' need delimiting due to the fact that they
+ * look like cell references.
+ * <p/>
+ * This code is currently being used for translating formulas represented with <code>Ptg</code>
+ * tokens into human readable text form. In formula expressions, a sheet name always has a
+ * trailing '!' so there is little chance for ambiguity. It doesn't matter too much what this
+ * method returns but it is worth noting the likely consumers of these formula text strings:
+ * <ol>
+ * <li>POI's own formula parser</li>
+ * <li>Visual reading by human</li>
+ * <li>VBA automation entry into Excel cell contents e.g. ActiveCell.Formula = "=c64!A1"</li>
+ * <li>Manual entry into Excel cell contents</li>
+ * <li>Some third party formula parser</li>
+ * </ol>
+ *
+ * At the time of writing, POI's formula parser tolerates cell-like sheet names in formulas
+ * with or without delimiters. The same goes for Excel(2007), both manual and automated entry.
+ * <p/>
+ * For better or worse this implementation attempts to replicate Excel's formula renderer.
+ * Excel uses range checking on the apparent 'row' and 'column' components. Note however that
+ * the maximum sheet size varies across versions:
+ * <p/>
+ * <blockquote><table border="0" cellpadding="1" cellspacing="0"
+ * summary="Notable cases.">
+ * <tr><th>Version </th><th>File Format </th>
+ * <th>Last Column </th><th>Last Row</th></tr>
+ * <tr><td>97-2003</td><td>BIFF8</td><td>"IV" (2^8)</td><td>65536 (2^14)</td></tr>
+ * <tr><td>2007</td><td>BIFF12</td><td>"XFD" (2^14)</td><td>1048576 (2^20)</td></tr>
+ * </table></blockquote>
+ * POI currently targets BIFF8 (Excel 97-2003), so the following behaviour can be observed for
+ * this method:
+ * <blockquote><table border="0" cellpadding="1" cellspacing="0"
+ * summary="Notable cases.">
+ * <tr><th>Input </th>
+ * <th>Result </th></tr>
+ * <tr><td>"A1", 1</td><td>true</td></tr>
+ * <tr><td>"a111", 1</td><td>true</td></tr>
+ * <tr><td>"A65536", 1</td><td>true</td></tr>
+ * <tr><td>"A65537", 1</td><td>false</td></tr>
+ * <tr><td>"iv1", 2</td><td>true</td></tr>
+ * <tr><td>"IW1", 2</td><td>false</td></tr>
+ * <tr><td>"AAA1", 3</td><td>false</td></tr>
+ * <tr><td>"a111", 1</td><td>true</td></tr>
+ * <tr><td>"Sheet1", 6</td><td>false</td></tr>
+ * </table></blockquote>
+ */
+ /* package */ static boolean cellReferenceIsWithinRange(String rawSheetName, int numberOfLetters) {
+
+ if(numberOfLetters > BIFF8_LAST_COLUMN_TEXT_LEN) {
+ // "Sheet1" case etc
+ return false; // that was easy
+ }
+ int nDigits = rawSheetName.length() - numberOfLetters;
+ if(nDigits > BIFF8_LAST_ROW_TEXT_LEN) {
+ return false;
+ }
+ if(numberOfLetters == BIFF8_LAST_COLUMN_TEXT_LEN) {
+ String colStr = rawSheetName.substring(0, BIFF8_LAST_COLUMN_TEXT_LEN).toUpperCase();
+ if(colStr.compareTo(BIFF8_LAST_COLUMN) > 0) {
+ return false;
+ }
+ } else {
+ // apparent column name has less chars than max
+ // no need to check range
+ }
+
+ if(nDigits == BIFF8_LAST_ROW_TEXT_LEN) {
+ String colStr = rawSheetName.substring(numberOfLetters);
+ // ASCII comparison is valid if digit count is same
+ if(colStr.compareTo(BIFF8_LAST_ROW) > 0) {
+ return false;
+ }
+ } else {
+ // apparent row has less chars than max
+ // no need to check range
+ }
+
+ return true;
+ }
+
+ /**
+ * Note - this method assumes the specified rawSheetName has only letters and digits. It
+ * cannot be used to match absolute or range references (using the dollar or colon char).
+ * <p/>
+ * Some notable cases:
+ * <blockquote><table border="0" cellpadding="1" cellspacing="0"
+ * summary="Notable cases.">
+ * <tr><th>Input </th><th>Result </th><th>Comments</th></tr>
+ * <tr><td>"A1" </td><td>true</td><td> </td></tr>
+ * <tr><td>"a111" </td><td>true</td><td> </td></tr>
+ * <tr><td>"AA" </td><td>false</td><td> </td></tr>
+ * <tr><td>"aa1" </td><td>true</td><td> </td></tr>
+ * <tr><td>"A1A" </td><td>false</td><td> </td></tr>
+ * <tr><td>"A1A1" </td><td>false</td><td> </td></tr>
+ * <tr><td>"A$1:$C$20" </td><td>false</td><td>Not a plain cell reference</td></tr>
+ * <tr><td>"SALES20080101" </td><td>true</td>
+ * <td>Still needs delimiting even though well out of range</td></tr>
+ * </table></blockquote>
+ *
+ * @return <code>true</code> if there is any possible ambiguity that the specified rawSheetName
+ * could be interpreted as a valid cell name.
+ */
+ /* package */ static boolean nameLooksLikePlainCellReference(String rawSheetName) {
+ Matcher matcher = CELL_REF_PATTERN.matcher(rawSheetName);
+ if(!matcher.matches()) {
+ return false;
+ }
+
+ // rawSheetName == "Sheet1" gets this far.
+ String lettersPrefix = matcher.group(1);
+ return cellReferenceIsWithinRange(rawSheetName, lettersPrefix.length());
+ }
+
+}
public class UnknownPtg
extends Ptg
{
- private short size;
+ private short size = 1;
/** Creates new UnknownPtg */
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;
}
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){
+
+ }
}
}
/**
- * 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;
--- /dev/null
+/* ====================================================================\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to You under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+==================================================================== */\r
+package org.apache.poi.hssf.usermodel;\r
+\r
+import org.apache.poi.hssf.record.EscherAggregate;\r
+import org.apache.poi.hssf.record.NoteRecord;\r
+import org.apache.poi.hssf.record.TextObjectRecord;\r
+import org.apache.poi.hssf.record.HyperlinkRecord;\r
+import org.apache.poi.ddf.*;\r
+\r
+import java.util.Map;\r
+import java.util.List;\r
+import java.util.Iterator;\r
+\r
+/**\r
+ * Represents a hyperlink.\r
+ *\r
+ * @author Yegor Kozlov\r
+ */\r
+public class HSSFHyperlink {\r
+\r
+ /**\r
+ * Link to a existing file or web page\r
+ */\r
+ public static final int LINK_URL = 1;\r
+\r
+ /**\r
+ * Link to a place in this document\r
+ */\r
+ public static final int LINK_DOCUMENT = 2;\r
+\r
+ /**\r
+ * Link to an E-mail address\r
+ */\r
+ public static final int LINK_EMAIL = 3;\r
+\r
+ /**\r
+ * Unknown type\r
+ */\r
+ public static final int LINK_UNKNOWN = 4;\r
+\r
+ /**\r
+ * Low-level record object that stores the actual hyperlink data\r
+ */\r
+ private HyperlinkRecord record = null;\r
+\r
+ protected HSSFHyperlink( HyperlinkRecord record )\r
+ {\r
+ this.record = record;\r
+ }\r
+\r
+ /**\r
+ * Return the row of the cell that contains the hyperlink\r
+ *\r
+ * @return the 0-based row of the cell that contains the hyperlink\r
+ */\r
+ public int getRow(){\r
+ return record.getRow();\r
+ }\r
+\r
+ /**\r
+ * Set the row of the cell that contains the hyperlink\r
+ *\r
+ * @param row the 0-based row of the cell that contains the hyperlink\r
+ */\r
+ public void setRow(int row){\r
+ record.setRow(row);\r
+ }\r
+\r
+ /**\r
+ * Return the column of the cell that contains the hyperlink\r
+ *\r
+ * @return the 0-based column of the cell that contains the hyperlink\r
+ */\r
+ public short getColumn(){\r
+ return record.getColumn();\r
+ }\r
+\r
+ /**\r
+ * Set the column of the cell that contains the hyperlink\r
+ *\r
+ * @param col the 0-based column of the cell that contains the hyperlink\r
+ */\r
+ public void setColumn(short col){\r
+ record.setColumn(col);\r
+ }\r
+\r
+ /**\r
+ * Hypelink address. Depending on the hyperlink type it can be URL, e-mail, etc.\r
+ *\r
+ * @return the address of this hyperlink\r
+ */\r
+ public String getAddress(){\r
+ return record.getUrlString();\r
+ }\r
+\r
+ /**\r
+ * Return text to display for this hyperlink\r
+ *\r
+ * @return text to display\r
+ */\r
+ public String getLabel(){\r
+ return record.getLabel();\r
+ }\r
+\r
+ /**\r
+ * Return the type of this hyperlink\r
+ *\r
+ * @return the type of this hyperlink\r
+ */\r
+ public int getType(){\r
+ throw new RuntimeException("Not implemented");\r
+ }\r
+}\r
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
* @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)
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);
+ }
+ }
}
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
}
/**
- * 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);
}
/**
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);
+ }
+ }
}
/**
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.*;
* @author Yegor Kozlov
*/
public class Fill {
+ // For logging
+ protected POILogger logger = POILogFactory.getLogger(this.getClass());
+
/**
* Fill with a solid color
*/
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;
}
/**
*/
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();
}
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;
SlideAtom sa = getSlideRecord().getSlideAtom();
return sa.getFollowMasterBackground();
}
+
+ public Background getBackground() {
+ if(getFollowMasterBackground())
+ return getMasterSheet().getBackground();
+ else
+ return super.getBackground();
+ }
}
* 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);
+ }
}
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-/*
- * Created on May 15, 2005
+
+
+package org.apache.poi.hssf.record.formula.functions;
+
+import org.apache.poi.hssf.record.formula.eval.AreaEval;
+import org.apache.poi.hssf.record.formula.eval.BlankEval;
+import org.apache.poi.hssf.record.formula.eval.ErrorEval;
+import org.apache.poi.hssf.record.formula.eval.Eval;
+import org.apache.poi.hssf.record.formula.eval.NumberEval;
+import org.apache.poi.hssf.record.formula.eval.RefEval;
+import org.apache.poi.hssf.record.formula.eval.StringEval;
+import org.apache.poi.hssf.record.formula.eval.ValueEval;
+
+/**
+ * Counts the number of cells that contain data within the list of arguments.
*
+ * Excel Syntax
+ * COUNTA(value1,value2,...)
+ * Value1, value2, ... are 1 to 30 arguments representing the values or ranges to be counted.
+ *
+ * @author Josh Micich
*/
-package org.apache.poi.hssf.record.formula.functions;
+public final class Counta implements Function {
+
+ public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
+ int nArgs = args.length;
+ if (nArgs < 1) {
+ // too few arguments
+ return ErrorEval.VALUE_INVALID;
+ }
+
+ if (nArgs > 30) {
+ // too many arguments
+ return ErrorEval.VALUE_INVALID;
+ }
+
+ int temp = 0;
+ // Note - observed behavior of Excel:
+ // Error values like #VALUE!, #REF!, #DIV/0!, #NAME? etc don't cause this COUNTA to return an error
+ // in fact, they seem to get counted
+
+ for(int i=0; i<nArgs; i++) {
+ temp += countArg(args[i]);
+
+ }
+ return new NumberEval(temp);
+ }
+
+ private static int countArg(Eval eval) {
+ if (eval instanceof AreaEval) {
+ AreaEval ae = (AreaEval) eval;
+ return countAreaEval(ae);
+ }
+ if (eval instanceof RefEval) {
+ RefEval refEval = (RefEval)eval;
+ return countValue(refEval.getInnerValueEval());
+ }
+ if (eval instanceof NumberEval) {
+ return 1;
+ }
+ if (eval instanceof StringEval) {
+ return 1;
+ }
+
+
+ throw new RuntimeException("Unexpected eval type (" + eval.getClass().getName() + ")");
+ }
+
+ private static int countAreaEval(AreaEval ae) {
+
+ int temp = 0;
+ ValueEval[] values = ae.getValues();
+ for (int i = 0; i < values.length; i++) {
+ ValueEval val = values[i];
+ if(val == null) {
+ // seems to occur. Really we would have expected BlankEval
+ continue;
+ }
+ temp += countValue(val);
+
+ }
+ return temp;
+ }
+
+ private static int countValue(ValueEval valueEval) {
+
+ if(valueEval == BlankEval.INSTANCE) {
+ return 0;
+ }
+
+ if(valueEval instanceof BlankEval) {
+ // wouldn't need this if BlankEval was final
+ return 0;
+ }
-public class Counta extends NotImplementedFunction {
+ if(valueEval instanceof ErrorEval) {
+ // note - error values are counted
+ return 1;
+ }
+ // also empty strings and zeros are counted too
+ return 1;
+ }
}
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-/*
- * Created on May 15, 2005
- *
- */
+
+
package org.apache.poi.hssf.record.formula.functions;
-public class Countif extends NotImplementedFunction {
+import org.apache.poi.hssf.record.formula.eval.AreaEval;
+import org.apache.poi.hssf.record.formula.eval.BoolEval;
+import org.apache.poi.hssf.record.formula.eval.ErrorEval;
+import org.apache.poi.hssf.record.formula.eval.Eval;
+import org.apache.poi.hssf.record.formula.eval.NumberEval;
+import org.apache.poi.hssf.record.formula.eval.RefEval;
+import org.apache.poi.hssf.record.formula.eval.StringEval;
+import org.apache.poi.hssf.record.formula.eval.ValueEval;
+
+/**
+ * Implementation for the function COUNTIF<p/>
+ *
+ * Syntax: COUNTIF ( range, criteria )
+ * <table border="0" cellpadding="1" cellspacing="0" summary="Parameter descriptions">
+ * <tr><th>range </th><td>is the range of cells to be counted based on the criteria</td></tr>
+ * <tr><th>criteria</th><td>is used to determine which cells to count</td></tr>
+ * </table>
+ * <p/>
+ *
+ * @author Josh Micich
+ */
+public final class Countif implements Function {
+
+ /**
+ * Common interface for the matching criteria.
+ */
+ private interface I_MatchPredicate {
+ boolean matches(Eval x);
+ }
+
+ private static final class NumberMatcher implements I_MatchPredicate {
+
+ private final double _value;
+
+ public NumberMatcher(double value) {
+ _value = value;
+ }
+
+ public boolean matches(Eval x) {
+ if(x instanceof StringEval) {
+ // if the target(x) is a string, but parses as a number
+ // it may still count as a match
+ StringEval se = (StringEval)x;
+ Double val = parseDouble(se.getStringValue());
+ if(val == null) {
+ // x is text that is not a number
+ return false;
+ }
+ return val.doubleValue() == _value;
+ }
+ if(!(x instanceof NumberEval)) {
+ return false;
+ }
+ NumberEval ne = (NumberEval) x;
+ return ne.getNumberValue() == _value;
+ }
+ }
+ private static final class BooleanMatcher implements I_MatchPredicate {
+
+ private final boolean _value;
+
+ public BooleanMatcher(boolean value) {
+ _value = value;
+ }
+
+ public boolean matches(Eval x) {
+ if(x instanceof StringEval) {
+ StringEval se = (StringEval)x;
+ Boolean val = parseBoolean(se.getStringValue());
+ if(val == null) {
+ // x is text that is not a boolean
+ return false;
+ }
+ if (true) { // change to false to observe more intuitive behaviour
+ // Note - Unlike with numbers, it seems that COUNTA never matches
+ // boolean values when the target(x) is a string
+ return false;
+ }
+ return val.booleanValue() == _value;
+ }
+ if(!(x instanceof BoolEval)) {
+ return false;
+ }
+ BoolEval be = (BoolEval) x;
+ return be.getBooleanValue() == _value;
+ }
+ }
+ private static final class StringMatcher implements I_MatchPredicate {
+
+ private final String _value;
+
+ public StringMatcher(String value) {
+ _value = value;
+ }
+
+ public boolean matches(Eval x) {
+ if(!(x instanceof StringEval)) {
+ return false;
+ }
+ StringEval se = (StringEval) x;
+ return se.getStringValue() == _value;
+ }
+ }
+
+ public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
+ switch(args.length) {
+ case 2:
+ // expected
+ break;
+ default:
+ // TODO - it doesn't seem to be possible to enter COUNTIF() into Excel with the wrong arg count
+ // perhaps this should be an exception
+ return ErrorEval.VALUE_INVALID;
+ }
+
+ AreaEval range = (AreaEval) args[0];
+ Eval criteriaArg = args[1];
+ if(criteriaArg instanceof RefEval) {
+ // criteria is not a literal value, but a cell reference
+ // for example COUNTIF(B2:D4, E1)
+ RefEval re = (RefEval)criteriaArg;
+ criteriaArg = re.getInnerValueEval();
+ } else {
+ // other non literal tokens such as function calls, have been fully evaluated
+ // for example COUNTIF(B2:D4, COLUMN(E1))
+ }
+ I_MatchPredicate mp = createCriteriaPredicate(criteriaArg);
+ return countMatchingCellsInArea(range, mp);
+ }
+ /**
+ * @return the number of evaluated cells in the range that match the specified criteria
+ */
+ private Eval countMatchingCellsInArea(AreaEval range, I_MatchPredicate criteriaPredicate) {
+ ValueEval[] values = range.getValues();
+ int result = 0;
+ for (int i = 0; i < values.length; i++) {
+ if(criteriaPredicate.matches(values[i])) {
+ result++;
+ }
+ }
+ return new NumberEval(result);
+ }
+
+ private static I_MatchPredicate createCriteriaPredicate(Eval evaluatedCriteriaArg) {
+ if(evaluatedCriteriaArg instanceof NumberEval) {
+ return new NumberMatcher(((NumberEval)evaluatedCriteriaArg).getNumberValue());
+ }
+ if(evaluatedCriteriaArg instanceof BoolEval) {
+ return new BooleanMatcher(((BoolEval)evaluatedCriteriaArg).getBooleanValue());
+ }
+
+ if(evaluatedCriteriaArg instanceof StringEval) {
+ return createGeneralMatchPredicate((StringEval)evaluatedCriteriaArg);
+ }
+ throw new RuntimeException("Unexpected type for criteria ("
+ + evaluatedCriteriaArg.getClass().getName() + ")");
+ }
+
+ /**
+ * When the second argument is a string, many things are possible
+ */
+ private static I_MatchPredicate createGeneralMatchPredicate(StringEval stringEval) {
+ String value = stringEval.getStringValue();
+ char firstChar = value.charAt(0);
+ Boolean booleanVal = parseBoolean(value);
+ if(booleanVal != null) {
+ return new BooleanMatcher(booleanVal.booleanValue());
+ }
+
+ Double doubleVal = parseDouble(value);
+ if(doubleVal != null) {
+ return new NumberMatcher(doubleVal.doubleValue());
+ }
+ switch(firstChar) {
+ case '>':
+ case '<':
+ case '=':
+ throw new RuntimeException("Incomplete code - criteria expressions such as '"
+ + value + "' not supported yet");
+ }
+
+ //else - just a plain string with no interpretation.
+ return new StringMatcher(value);
+ }
+ /**
+ * Under certain circumstances COUNTA will equate a plain number with a string representation of that number
+ */
+ /* package */ static Double parseDouble(String strRep) {
+ if(!Character.isDigit(strRep.charAt(0))) {
+ // avoid using NumberFormatException to tell when string is not a number
+ return null;
+ }
+ // TODO - support notation like '1E3' (==1000)
+
+ double val;
+ try {
+ val = Double.parseDouble(strRep);
+ } catch (NumberFormatException e) {
+ return null;
+ }
+ return new Double(val);
+ }
+ /**
+ * Boolean literals ('TRUE', 'FALSE') treated similarly but NOT same as numbers.
+ */
+ /* package */ static Boolean parseBoolean(String strRep) {
+ switch(strRep.charAt(0)) {
+ case 't':
+ case 'T':
+ if("TRUE".equalsIgnoreCase(strRep)) {
+ return Boolean.TRUE;
+ }
+ break;
+ case 'f':
+ case 'F':
+ if("FALSE".equalsIgnoreCase(strRep)) {
+ return Boolean.FALSE;
+ }
+ break;
+ }
+ return null;
+ }
}
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-/*
- * Created on May 15, 2005
- *
- */
+
+
package org.apache.poi.hssf.record.formula.functions;
-public class Index extends NotImplementedFunction {
+import org.apache.poi.hssf.record.formula.eval.AreaEval;
+import org.apache.poi.hssf.record.formula.eval.ErrorEval;
+import org.apache.poi.hssf.record.formula.eval.Eval;
+import org.apache.poi.hssf.record.formula.eval.NumberEval;
+import org.apache.poi.hssf.record.formula.eval.RefEval;
+
+/**
+ * Implementation for the Excel function INDEX<p/>
+ *
+ * Syntax : <br/>
+ * INDEX ( reference, row_num[, column_num [, area_num]])</br>
+ * INDEX ( array, row_num[, column_num])
+ * <table border="0" cellpadding="1" cellspacing="0" summary="Parameter descriptions">
+ * <tr><th>reference</th><td>typically an area reference, possibly a union of areas</td></tr>
+ * <tr><th>array</th><td>a literal array value (currently not supported)</td></tr>
+ * <tr><th>row_num</th><td>selects the row within the array or area reference</td></tr>
+ * <tr><th>column_num</th><td>selects column within the array or area reference. default is 1</td></tr>
+ * <tr><th>area_num</th><td>used when reference is a union of areas</td></tr>
+ * </table>
+ * <p/>
+ *
+ * @author Josh Micich
+ */
+public final class Index implements Function {
+ // TODO - javadoc for interface method
+ public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
+ int nArgs = args.length;
+ if(nArgs < 2) {
+ // too few arguments
+ return ErrorEval.VALUE_INVALID;
+ }
+ Eval firstArg = args[0];
+ if(firstArg instanceof AreaEval) {
+ AreaEval reference = (AreaEval) firstArg;
+
+ int rowIx = 0;
+ int columnIx = 0;
+ int areaIx = 0;
+ switch(nArgs) {
+ case 4:
+ areaIx = convertIndexArgToZeroBase(args[3]);
+ throw new RuntimeException("Incomplete code" +
+ " - don't know how to support the 'area_num' parameter yet)");
+ // Excel expression might look like this "INDEX( (A1:B4, C3:D6, D2:E5 ), 1, 2, 3)
+ // In this example, the 3rd area would be used i.e. D2:E5, and the overall result would be E2
+ // Token array might be encoded like this: MemAreaPtg, AreaPtg, AreaPtg, UnionPtg, UnionPtg, ParenthesesPtg
+ // The formula parser doesn't seem to support this yet. Not sure if the evaluator does either
+
+ case 3:
+ columnIx = convertIndexArgToZeroBase(args[2]);
+ case 2:
+ rowIx = convertIndexArgToZeroBase(args[1]);
+ break;
+ default:
+ // too many arguments
+ return ErrorEval.VALUE_INVALID;
+ }
+
+ int nColumns = reference.getLastColumn()-reference.getFirstColumn()+1;
+ int index = rowIx * nColumns + columnIx;
+
+ return reference.getValues()[index];
+ }
+
+ // else the other variation of this function takes an array as the first argument
+ // it seems like interface 'ArrayEval' does not even exist yet
+
+ throw new RuntimeException("Incomplete code - cannot handle first arg of type ("
+ + firstArg.getClass().getName() + ")");
+ }
+
+ /**
+ * takes a NumberEval representing a 1-based index and returns the zero-based int value
+ */
+ private static int convertIndexArgToZeroBase(Eval ev) {
+ NumberEval ne;
+ if(ev instanceof RefEval) {
+ // TODO - write junit to justify this
+ RefEval re = (RefEval) ev;
+ ne = (NumberEval) re.getInnerValueEval();
+ } else {
+ ne = (NumberEval)ev;
+ }
+
+ return (int)ne.getNumberValue() - 1;
+ }
}
* 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);
+ }
}
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]);
assertEquals(tr1[i].getText(), tr2[i].getText());\r
}\r
}\r
+\r
+ /**\r
+ * Bug 44296: HSLF Not Extracting Slide Background Image\r
+ */\r
+ public void test44296 () throws Exception {\r
+ FileInputStream is = new FileInputStream(new File(cwd, "44296.ppt"));\r
+ SlideShow ppt = new SlideShow(is);\r
+ is.close();\r
+\r
+ Slide slide = ppt.getSlides()[0];\r
+\r
+ Background b = slide.getBackground();\r
+ Fill f = b.getFill();\r
+ assertEquals(Fill.FILL_PICTURE, f.getFillType());\r
+\r
+ PictureData pict = f.getPictureData();\r
+ assertNotNull(pict);\r
+ assertEquals(Picture.JPEG, pict.getType());\r
+ }\r
+\r
}\r
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+
+package org.apache.poi.hssf.record.formula.functions;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Direct tests for all implementors of <code>Function</code>.
+ *
+ * @author Josh Micich
+ */
+public final class AllIndividualFunctionEvaluationTests {
+
+ // TODO - have this suite incorporated into a higher level one
+ public static Test suite() {
+ TestSuite result = new TestSuite("Tests for org.apache.poi.hssf.record.formula.functions");
+ result.addTestSuite(TestCountFuncs.class);
+ result.addTestSuite(TestDate.class);
+ result.addTestSuite(TestFinanceLib.class);
+ result.addTestSuite(TestIndex.class);
+ result.addTestSuite(TestMathX.class);
+ result.addTestSuite(TestRowCol.class);
+ result.addTestSuite(TestStatsLib.class);
+ return result;
+ }
+
+}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+
+package org.apache.poi.hssf.record.formula.functions;
+
+import org.apache.poi.hssf.record.formula.AreaPtg;
+import org.apache.poi.hssf.record.formula.ReferencePtg;
+import org.apache.poi.hssf.record.formula.eval.Area2DEval;
+import org.apache.poi.hssf.record.formula.eval.AreaEval;
+import org.apache.poi.hssf.record.formula.eval.NumberEval;
+import org.apache.poi.hssf.record.formula.eval.Ref2DEval;
+import org.apache.poi.hssf.record.formula.eval.RefEval;
+import org.apache.poi.hssf.record.formula.eval.ValueEval;
+
+/**
+ * Test helper class for creating mock <code>Eval</code> objects
+ *
+ * @author Josh Micich
+ */
+final class EvalFactory {
+ private static final NumberEval ZERO = new NumberEval(0);
+
+ private EvalFactory() {
+ // no instances of this class
+ }
+
+ /**
+ * Creates a dummy AreaEval (filled with zeros)
+ * <p/>
+ * nCols and nRows could have been derived
+ */
+ public static AreaEval createAreaEval(String areaRefStr, int nCols, int nRows) {
+ int nValues = nCols * nRows;
+ ValueEval[] values = new ValueEval[nValues];
+ for (int i = 0; i < nValues; i++) {
+ values[i] = ZERO;
+ }
+
+ return new Area2DEval(new AreaPtg(areaRefStr), values);
+ }
+
+ /**
+ * Creates a single RefEval (with value zero)
+ */
+ public static RefEval createRefEval(String refStr) {
+ return new Ref2DEval(new ReferencePtg(refStr), ZERO, true);
+ }
+}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+
+package org.apache.poi.hssf.record.formula.functions;
+
+import junit.framework.AssertionFailedError;
+
+import org.apache.poi.hssf.record.formula.eval.ErrorEval;
+import org.apache.poi.hssf.record.formula.eval.Eval;
+import org.apache.poi.hssf.record.formula.eval.NumericValueEval;
+
+/**
+ * Test helper class for invoking functions with numeric results.
+ *
+ * @author Josh Micich
+ */
+final class NumericFunctionInvoker {
+
+ private NumericFunctionInvoker() {
+ // no instances of this class
+ }
+
+ private static final class NumericEvalEx extends Exception {
+ public NumericEvalEx(String msg) {
+ super(msg);
+ }
+ }
+
+ /**
+ * Invokes the specified function with the arguments.
+ * <p/>
+ * Assumes that the cell coordinate parameters of
+ * <code>Function.evaluate(args, srcCellRow, srcCellCol)</code>
+ * are not required.
+ * <p/>
+ * This method cannot be used for confirming error return codes. Any non-numeric evaluation
+ * result causes the current junit test to fail.
+ */
+ public static double invoke(Function f, Eval[] args) {
+ try {
+ return invokeInternal(f, args, -1, -1);
+ } catch (NumericEvalEx e) {
+ throw new AssertionFailedError("Evaluation of function (" + f.getClass().getName()
+ + ") failed: " + e.getMessage());
+ }
+
+ }
+ /**
+ * Formats nicer error messages for the junit output
+ */
+ private static double invokeInternal(Function f, Eval[] args, int srcCellRow, int srcCellCol)
+ throws NumericEvalEx {
+ Eval evalResult = f.evaluate(args, srcCellRow, (short)srcCellCol);
+ if(evalResult == null) {
+ throw new NumericEvalEx("Result object was null");
+ }
+ if(evalResult instanceof ErrorEval) {
+ ErrorEval ee = (ErrorEval) evalResult;
+ throw new NumericEvalEx(formatErrorMessage(ee));
+ }
+ if(!(evalResult instanceof NumericValueEval)) {
+ throw new NumericEvalEx("Result object type (" + evalResult.getClass().getName()
+ + ") is invalid. Expected implementor of ("
+ + NumericValueEval.class.getName() + ")");
+ }
+
+ NumericValueEval result = (NumericValueEval) evalResult;
+ return result.getNumberValue();
+ }
+ private static String formatErrorMessage(ErrorEval ee) {
+ if(errorCodesAreEqual(ee, ErrorEval.FUNCTION_NOT_IMPLEMENTED)) {
+ return "Function not implemented";
+ }
+ if(errorCodesAreEqual(ee, ErrorEval.UNKNOWN_ERROR)) {
+ return "Unknown error";
+ }
+ return "Error code=" + ee.getErrorCode();
+ }
+ private static boolean errorCodesAreEqual(ErrorEval a, ErrorEval b) {
+ if(a==b) {
+ return true;
+ }
+ return a.getErrorCode() == b.getErrorCode();
+ }
+
+}
--- /dev/null
+/* ====================================================================
+ 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);
+ }
+}
--- /dev/null
+/* ====================================================================
+ 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);
+ }
+}
--- /dev/null
+/* ====================================================================
+ 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);
+ }
+}
--- /dev/null
+package org.apache.poi.hssf.usermodel;\r
+/* ====================================================================\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to You under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+==================================================================== */\r
+\r
+import junit.framework.TestCase;\r
+\r
+import java.io.IOException;\r
+import java.io.FileInputStream;\r
+import java.io.File;\r
+\r
+/**\r
+ * Bug 44297: 32767+32768 is evaluated to -1\r
+ * Fix: IntPtg must operate with unsigned short. Reading signed short results in incorrect formula calculation\r
+ * if a formula has values in the interval [Short.MAX_VALUE, (Short.MAX_VALUE+1)*2]\r
+ *\r
+ * @author Yegor Kozlov\r
+ */\r
+\r
+public class TestBug44297 extends TestCase {\r
+ protected String cwd = System.getProperty("HSSF.testdata.path");\r
+\r
+ public void test44297() throws IOException {\r
+ FileInputStream in = new FileInputStream(new File(cwd, "44297.xls"));\r
+ HSSFWorkbook wb = new HSSFWorkbook(in);\r
+ in.close();\r
+\r
+ HSSFRow row;\r
+ HSSFCell cell;\r
+\r
+ HSSFSheet sheet = wb.getSheetAt(0);\r
+\r
+ HSSFFormulaEvaluator eva = new HSSFFormulaEvaluator(sheet, wb);\r
+\r
+ row = (HSSFRow)sheet.getRow(0);\r
+ cell = row.getCell((short)0);\r
+ assertEquals("31+46", cell.getCellFormula());\r
+ eva.setCurrentRow(row);\r
+ assertEquals(77, eva.evaluate(cell).getNumberValue(), 0);\r
+\r
+ row = (HSSFRow)sheet.getRow(1);\r
+ cell = row.getCell((short)0);\r
+ assertEquals("30+53", cell.getCellFormula());\r
+ eva.setCurrentRow(row);\r
+ assertEquals(83, eva.evaluate(cell).getNumberValue(), 0);\r
+\r
+ row = (HSSFRow)sheet.getRow(2);\r
+ cell = row.getCell((short)0);\r
+ assertEquals("SUM(A1:A2)", cell.getCellFormula());\r
+ eva.setCurrentRow(row);\r
+ assertEquals(160, eva.evaluate(cell).getNumberValue(), 0);\r
+\r
+ row = (HSSFRow)sheet.getRow(4);\r
+ cell = row.getCell((short)0);\r
+ assertEquals("32767+32768", cell.getCellFormula());\r
+ eva.setCurrentRow(row);\r
+ assertEquals(65535, eva.evaluate(cell).getNumberValue(), 0);\r
+\r
+ row = (HSSFRow)sheet.getRow(7);\r
+ cell = row.getCell((short)0);\r
+ assertEquals("32744+42333", cell.getCellFormula());\r
+ eva.setCurrentRow(row);\r
+ assertEquals(75077, eva.evaluate(cell).getNumberValue(), 0);\r
+\r
+ row = (HSSFRow)sheet.getRow(8);\r
+ cell = row.getCell((short)0);\r
+ assertEquals("327680.0/32768", cell.getCellFormula());\r
+ eva.setCurrentRow(row);\r
+ assertEquals(10, eva.evaluate(cell).getNumberValue(), 0);\r
+\r
+ row = (HSSFRow)sheet.getRow(9);\r
+ cell = row.getCell((short)0);\r
+ assertEquals("32767+32769", cell.getCellFormula());\r
+ eva.setCurrentRow(row);\r
+ assertEquals(65536, eva.evaluate(cell).getNumberValue(), 0);\r
+\r
+ row = (HSSFRow)sheet.getRow(10);\r
+ cell = row.getCell((short)0);\r
+ assertEquals("35000+36000", cell.getCellFormula());\r
+ eva.setCurrentRow(row);\r
+ assertEquals(71000, eva.evaluate(cell).getNumberValue(), 0);\r
+\r
+ row = (HSSFRow)sheet.getRow(11);\r
+ cell = row.getCell((short)0);\r
+ assertEquals("-1000000.0-3000000.0", cell.getCellFormula());\r
+ eva.setCurrentRow(row);\r
+ assertEquals(-4000000, eva.evaluate(cell).getNumberValue(), 0);\r
+ }\r
+\r
+}\r
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());
}
}
}
+
+ /**
+ * 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());
+ }
}
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;
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));
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;
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 {
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
for(int i=0; i<r.length; i++) {
assertFalse( r[i] instanceof ContinueRecord );
}
+
+ // Check that the last few records are as we expect
+ // (Makes sure we don't accidently skip the end ones)
+ int numRec = mockListen.records.size();
+ assertEquals(DVALRecord.class, mockListen.records.get(numRec-3).getClass());
+ assertEquals(DVRecord.class, mockListen.records.get(numRec-2).getClass());
+ assertEquals(EOFRecord.class, mockListen.records.get(numRec-1).getClass());
}
/**
fp = new FormulaParser("40000", null);
fp.parse();
ptg=fp.getRPNPtg();
- assertTrue("ptg should be Number, is "+ptg[0].getClass(), ptg[0] instanceof NumberPtg);
+ assertTrue("ptg should be IntPtg, is "+ptg[0].getClass(), ptg[0] instanceof IntPtg);
}
+
/** bug 33160, testcase by Amol Deshmukh*/
public void testSimpleLongFormula() {
FormulaParser fp = new FormulaParser("40000/2", null);
fp.parse();
Ptg[] ptgs = fp.getRPNPtg();
assertTrue("three tokens expected, got "+ptgs.length,ptgs.length == 3);
- assertTrue("NumberPtg",(ptgs[0] instanceof NumberPtg));
+ assertTrue("IntPtg",(ptgs[0] instanceof IntPtg));
assertTrue("IntPtg",(ptgs[1] instanceof IntPtg));
assertTrue("DividePtg",(ptgs[2] instanceof DividePtg));
}
package org.apache.poi.hssf.record;
+import java.io.ByteArrayInputStream;
+
+import org.apache.poi.hssf.record.formula.AttrPtg;
+import org.apache.poi.hssf.record.formula.ConcatPtg;
+import org.apache.poi.hssf.record.formula.FuncVarPtg;
+import org.apache.poi.hssf.record.formula.IntPtg;
+import org.apache.poi.hssf.record.formula.RangePtg;
+import org.apache.poi.hssf.record.formula.ReferencePtg;
+import org.apache.poi.hssf.record.formula.UnknownPtg;
+
import junit.framework.TestCase;
/**
assertEquals("Offset 22", 1, output[26]);
}
+ public void testWithConcat() throws Exception {
+ // =CHOOSE(2,A2,A3,A4)
+ byte[] data = new byte[] {
+ 6, 0, 68, 0,
+ 1, 0, 1, 0, 15, 0, 0, 0, 0, 0, 0, 0, 57,
+ 64, 0, 0, 12, 0, 12, -4, 46, 0,
+ 30, 2, 0, // Int - 2
+ 25, 4, 3, 0, // Attr
+ 8, 0, // Concat
+ 17, 0, // Range
+ 26, 0, 35, 0, // Bit like an attr
+ 36, 1, 0, 0, -64, // Ref - A2
+ 25, 8, 21, 0, // Attr
+ 36, 2, 0, 0, -64, // Ref - A3
+ 25, 8, 12, 0, // Attr
+ 36, 3, 0, 0, -64, // Ref - A4
+ 25, 8, 3, 0, // Attr
+ 66, 4, 100, 0 // CHOOSE
+ };
+ RecordInputStream inp = new RecordInputStream(
+ new ByteArrayInputStream(data)
+ );
+ inp.nextRecord();
+
+ FormulaRecord fr = new FormulaRecord(inp);
+
+ assertEquals(14, fr.getNumberOfExpressionTokens());
+ assertEquals(IntPtg.class, fr.getParsedExpression().get(0).getClass());
+ assertEquals(AttrPtg.class, fr.getParsedExpression().get(1).getClass());
+ assertEquals(ConcatPtg.class, fr.getParsedExpression().get(2).getClass());
+ assertEquals(UnknownPtg.class, fr.getParsedExpression().get(3).getClass());
+ assertEquals(RangePtg.class, fr.getParsedExpression().get(4).getClass());
+ assertEquals(UnknownPtg.class, fr.getParsedExpression().get(5).getClass());
+ assertEquals(AttrPtg.class, fr.getParsedExpression().get(6).getClass());
+ assertEquals(ReferencePtg.class, fr.getParsedExpression().get(7).getClass());
+ assertEquals(AttrPtg.class, fr.getParsedExpression().get(8).getClass());
+ assertEquals(ReferencePtg.class, fr.getParsedExpression().get(9).getClass());
+ assertEquals(AttrPtg.class, fr.getParsedExpression().get(10).getClass());
+ assertEquals(ReferencePtg.class, fr.getParsedExpression().get(11).getClass());
+ assertEquals(AttrPtg.class, fr.getParsedExpression().get(12).getClass());
+ assertEquals(FuncVarPtg.class, fr.getParsedExpression().get(13).getClass());
+
+ FuncVarPtg choose = (FuncVarPtg)fr.getParsedExpression().get(13);
+ assertEquals("CHOOSE", choose.getName());
+ }
+
public static void main(String [] ignored_args)
{
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.hssf.record;
+
+import java.io.ByteArrayInputStream;
+import java.net.URL;
+
+import junit.framework.TestCase;
+
+public class TestHyperlinkRecord extends TestCase {
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ private byte[] data = new byte[] {
+ -72, 1, 110, 0,
+ // ??, Row, col, xf
+ 6, 0, 3, 0, 2, 0, 2, 0,
+
+ // ??
+ -48, -55, -22, 121, -7, -70, -50, 17,
+ -116, -126, 0, -86, 0, 75, -87, 11,
+ 2, 0, 0, 0,
+
+ // URL length
+ 23, 0, 0, 0,
+
+ // Label length
+ 4, 0, 0, 0,
+
+ // Label
+ 76, 0, 44, 0, 65, 0, 0, 0,
+
+ // ??
+ -32, -55, -22, 121, -7, -70, -50, 17,
+ -116, -126, 0, -86, 0, 75, -87, 11,
+ 46, 0, 0, 0,
+
+ // URL
+ 104, 0, 116, 0, 116, 0, 112, 0, 58, 0, 47, 0, 47, 0, 119,
+ 0, 119, 0, 119, 0, 46, 0, 108, 0, 97, 0, 107, 0, 105,
+ 0, 110, 0, 103, 0, 115, 0, 46, 0, 99, 0, 111, 0,
+ 109, 0,
+ 0, 0 };
+
+ private byte[] data2 = new byte[] {
+ -72, 1, -126, 0,
+ // ??, Row, col, xf
+ 2, 0, 2, 0, 4, 0, 4, 0,
+
+ // ??
+ -48, -55, -22, 121, -7, -70, -50, 17,
+ -116, -126, 0, -86, 0, 75, -87, 11,
+ 2, 0, 0, 0,
+
+ // URL and Label lengths
+ 23, 0, 0, 0,
+ 15, 0, 0, 0,
+
+ // Label
+ 83, 0, 116, 0, 97, 0, 99, 0, 105, 0,
+ 101, 0, 64, 0, 65, 0, 66, 0, 67, 0,
+ 46, 0, 99, 0, 111, 0, 109, 0, 0, 0,
+
+ // ??
+ -32, -55, -22, 121, -7, -70, -50, 17,
+ -116, -126, 0, -86, 0, 75, -87, 11,
+ 44, 0, 0, 0,
+
+ // URL
+ 109, 0, 97, 0, 105, 0, 108, 0, 116, 0,
+ 111, 0, 58, 0, 83, 0, 116, 0, 97, 0,
+ 99, 0, 105, 0, 101, 0, 64, 0, 65, 0,
+ 66, 0, 67, 0, 46, 0, 99, 0, 111, 0,
+ 109, 0, 0, 0 };
+
+ public void testRecordParsing() throws Exception {
+ RecordInputStream inp = new RecordInputStream(
+ new ByteArrayInputStream(data)
+ );
+ inp.nextRecord();
+
+ HyperlinkRecord r = new HyperlinkRecord(inp);
+
+ assertEquals(3, r.getRow());
+ assertEquals(2, r.getColumn());
+ assertEquals(2, r.getXFIndex());
+
+ assertEquals("L,A", r.getLabel());
+ assertEquals("http://www.lakings.com", r.getUrlString());
+ assertEquals(new URL("http://www.lakings.com"), r.getUrl());
+
+ // Check it serialises as expected
+ assertEquals(data.length, r.getRecordSize());
+ byte[] d = r.serialize();
+ assertEquals(data.length, d.length);
+ for(int i=0; i<data.length; i++) {
+ assertEquals(data[i], d[i]);
+ }
+ }
+
+ public void testSecondRecord() throws Exception {
+ RecordInputStream inp = new RecordInputStream(
+ new ByteArrayInputStream(data2)
+ );
+ inp.nextRecord();
+
+ HyperlinkRecord r = new HyperlinkRecord(inp);
+
+ assertEquals(2, r.getRow());
+ assertEquals(4, r.getColumn());
+ assertEquals(4, r.getXFIndex());
+
+ assertEquals("Stacie@ABC.com", r.getLabel());
+ assertEquals("mailto:Stacie@ABC.com", r.getUrlString());
+ }
+}
import junit.framework.TestCase;
+import org.apache.poi.hssf.model.Workbook;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
/**
*
* @author Daniel Noll (daniel at nuix dot com dot au)
*/
-public class AbstractPtgTestCase extends TestCase
+public abstract class AbstractPtgTestCase extends TestCase
{
/** Directory containing the test data. */
private static String dataDir = System.getProperty("HSSF.testdata.path");
* @return the loaded workbook.
* @throws IOException if an error occurs loading the workbook.
*/
- protected static HSSFWorkbook loadWorkbook(String filename)
+ protected static final HSSFWorkbook loadWorkbook(String filename)
throws IOException {
File file = new File(dataDir, filename);
InputStream stream = new BufferedInputStream(new FileInputStream(file));
stream.close();
}
}
+
+ /**
+ * Creates a new Workbook and adds one sheet with the specified name
+ */
+ protected static final Workbook createWorkbookWithSheet(String sheetName) {
+
+ Workbook book = Workbook.createWorkbook();
+ // this creates sheet if it doesn't exist
+ book.checkExternSheet(0);
+ // TODO - this call alone does not create the sheet even though the javadoc says it does
+ book.setSheetName(0, sheetName);
+ return book;
+ }
+
}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+
+package org.apache.poi.hssf.record.formula;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Collects all tests for this package.
+ *
+ * @author Josh Micich
+ */
+public class AllFormulaTests {
+
+ public static Test suite() {
+ TestSuite result = new TestSuite("Tests for org.apache.poi.hssf.record.formula");
+ result.addTestSuite(TestArea3DPtg.class);
+ result.addTestSuite(TestAreaErrPtg.class);
+ result.addTestSuite(TestAreaPtg.class);
+ result.addTestSuite(TestErrPtg.class);
+ result.addTestSuite(TestFuncPtg.class);
+ result.addTestSuite(TestIntersectionPtg.class);
+ result.addTestSuite(TestPercentPtg.class);
+ result.addTestSuite(TestRangePtg.class);
+ result.addTestSuite(TestRef3DPtg.class);
+ result.addTestSuite(TestReferencePtg.class);
+ result.addTestSuite(TestSheetNameFormatter.class);
+ result.addTestSuite(TestUnionPtg.class);
+ return result;
+ }
+}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+
+package org.apache.poi.hssf.record.formula;
+
+import org.apache.poi.hssf.model.Workbook;
+
+/**
+ * Tests for Area3DPtg
+ *
+ * @author Josh Micich
+ */
+public final class TestArea3DPtg extends AbstractPtgTestCase {
+
+ /**
+ * confirms that sheet names get properly escaped
+ */
+ public void testToFormulaString() {
+
+ Area3DPtg target = new Area3DPtg("A1:B1", (short)0);
+
+ String sheetName = "my sheet";
+ Workbook book = createWorkbookWithSheet(sheetName);
+ assertEquals("'my sheet'!A1:B1", target.toFormulaString(book));
+
+ book.setSheetName(0, "Sheet1");
+ assertEquals("Sheet1!A1:B1", target.toFormulaString(book));
+
+ book.setSheetName(0, "C64");
+ assertEquals("'C64'!A1:B1", target.toFormulaString(book));
+ }
+
+
+
+}
--- /dev/null
+
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.record.formula;
+
+import junit.framework.TestCase;
+
+import org.apache.poi.hssf.model.FormulaParser;
+
+/**
+ * Tests for {@link AreaPtg}.
+ *
+ * @author Dmitriy Kumshayev
+ */
+public class TestAreaPtg extends TestCase
+{
+
+ AreaPtg relative;
+ AreaPtg absolute;
+
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ short firstRow=5;
+ short lastRow=13;
+ short firstCol=7;
+ short lastCol=17;
+ relative = new AreaPtg(firstRow,lastRow,firstCol,lastCol,true,true,true,true);
+ absolute = new AreaPtg(firstRow,lastRow,firstCol,lastCol,false,false,false,false);
+ }
+
+ public void testSetColumnsAbsolute()
+ {
+ resetColumns(absolute);
+ validateReference(true, absolute);
+ }
+ public void testSetColumnsRelative()
+ {
+ resetColumns(relative);
+ validateReference(false, relative);
+ }
+
+ private void validateReference(boolean abs, AreaPtg ref)
+ {
+ assertEquals("First column reference is not "+(abs?"absolute":"relative"),abs,!ref.isFirstColRelative());
+ assertEquals("Last column reference is not "+(abs?"absolute":"relative"),abs,!ref.isLastColRelative());
+ assertEquals("First row reference is not "+(abs?"absolute":"relative"),abs,!ref.isFirstRowRelative());
+ assertEquals("Last row reference is not "+(abs?"absolute":"relative"),abs,!ref.isLastRowRelative());
+ }
+
+
+ public void resetColumns(AreaPtg aptg)
+ {
+ short fc = aptg.getFirstColumn();
+ short lc = aptg.getLastColumn();
+ aptg.setFirstColumn(fc);
+ aptg.setLastColumn(lc);
+ assertEquals(fc , aptg.getFirstColumn() );
+ assertEquals(lc , aptg.getLastColumn() );
+ }
+
+ public void testFormulaParser()
+ {
+ String formula1="SUM($E$5:$E$6)";
+ String expectedFormula1="SUM($F$5:$F$6)";
+ String newFormula1 = shiftAllColumnsBy1(formula1);
+ assertEquals("Absolute references changed", expectedFormula1, newFormula1);
+
+ String formula2="SUM(E5:E6)";
+ String expectedFormula2="SUM(F5:F6)";
+ String newFormula2 = shiftAllColumnsBy1(formula2);
+ assertEquals("Relative references changed", expectedFormula2, newFormula2);
+ }
+
+ private String shiftAllColumnsBy1(String formula)
+ {
+ int letUsShiftColumn1By1Column=1;
+
+ FormulaParser parser = new FormulaParser(formula,null);
+ parser.parse();
+
+ final Ptg[] ptgs = parser.getRPNPtg();
+ for(int i=0; i<ptgs.length; i++)
+ {
+ Ptg ptg = ptgs[i];
+ if (ptg instanceof AreaPtg )
+ {
+ AreaPtg aptg = (AreaPtg)ptg;
+ aptg.setFirstColumn((short)(aptg.getFirstColumn()+letUsShiftColumn1By1Column));
+ aptg.setLastColumn((short)(aptg.getLastColumn()+letUsShiftColumn1By1Column));
+ }
+ }
+ String newFormula = parser.toFormulaString(ptgs);
+ return newFormula;
+ }
+
+
+
+}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+
+package org.apache.poi.hssf.record.formula;
+
+import org.apache.poi.hssf.model.Workbook;
+
+/**
+ * Tests for Ref3DPtg
+ *
+ * @author Josh Micich
+ */
+public final class TestRef3DPtg extends AbstractPtgTestCase {
+
+ public void testToFormulaString() {
+
+ Ref3DPtg target = new Ref3DPtg("A1", (short)0);
+
+ Workbook book = createWorkbookWithSheet("my sheet");
+
+ assertEquals("'my sheet'!A1", target.toFormulaString(book));
+
+ book.setSheetName(0, "ProfitAndLoss");
+ assertEquals("ProfitAndLoss!A1", target.toFormulaString(book));
+
+ book.setSheetName(0, "profit+loss");
+ assertEquals("'profit+loss'!A1", target.toFormulaString(book));
+ }
+}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+
+package org.apache.poi.hssf.record.formula;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for SheetNameFormatter
+ *
+ * @author Josh Micich
+ */
+public final class TestSheetNameFormatter extends TestCase {
+
+ public TestSheetNameFormatter(String testName) {
+ super(testName);
+ }
+
+ private static void confirmFormat(String rawSheetName, String expectedSheetNameEncoding) {
+ assertEquals(expectedSheetNameEncoding, SheetNameFormatter.format(rawSheetName));
+ }
+
+ /**
+ * Tests main public method 'format'
+ */
+ public void testFormat() {
+
+ confirmFormat("abc", "abc");
+ confirmFormat("123", "'123'");
+
+ confirmFormat("my sheet", "'my sheet'"); // space
+ confirmFormat("A:MEM", "'A:MEM'"); // colon
+
+ confirmFormat("O'Brian", "'O''Brian'"); // single quote gets doubled
+
+
+ confirmFormat("3rdTimeLucky", "'3rdTimeLucky'"); // digit in first pos
+ confirmFormat("_", "_"); // plain underscore OK
+ confirmFormat("my_3rd_sheet", "my_3rd_sheet"); // underscores and digits OK
+ confirmFormat("A12220", "'A12220'");
+ confirmFormat("TAXRETURN19980415", "TAXRETURN19980415");
+ }
+
+ private static void confirmCellNameMatch(String rawSheetName, boolean expected) {
+ assertEquals(expected, SheetNameFormatter.nameLooksLikePlainCellReference(rawSheetName));
+ }
+
+ /**
+ * Tests functionality to determine whether a sheet name containing only letters and digits
+ * would look (to Excel) like a cell name.
+ */
+ public void testLooksLikePlainCellReference() {
+
+ confirmCellNameMatch("A1", true);
+ confirmCellNameMatch("a111", true);
+ confirmCellNameMatch("AA", false);
+ confirmCellNameMatch("aa1", true);
+ confirmCellNameMatch("A1A", false);
+ confirmCellNameMatch("A1A1", false);
+ confirmCellNameMatch("SALES20080101", false); // out of range
+ }
+
+ private static void confirmCellRange(String text, int numberOfPrefixLetters, boolean expected) {
+ assertEquals(expected, SheetNameFormatter.cellReferenceIsWithinRange(text, numberOfPrefixLetters));
+ }
+
+ /**
+ * Tests exact boundaries for names that look very close to cell names (i.e. contain 1 or more
+ * letters followed by one or more digits).
+ */
+ public void testCellRange() {
+ confirmCellRange("A1", 1, true);
+ confirmCellRange("a111", 1, true);
+ confirmCellRange("A65536", 1, true);
+ confirmCellRange("A65537", 1, false);
+ confirmCellRange("iv1", 2, true);
+ confirmCellRange("IW1", 2, false);
+ confirmCellRange("AAA1", 3, false);
+ confirmCellRange("a111", 1, true);
+ confirmCellRange("Sheet1", 6, false);
+ confirmCellRange("iV65536", 2, true); // max cell in Excel 97-2003
+ confirmCellRange("IW65537", 2, false);
+ }
+}
wb = new HSSFWorkbook(new ByteArrayInputStream(out.toByteArray()));
assertTrue("No Exceptions while reading file", true);
-
}
+ /**
+ * Bug 42618: RecordFormatException reading a file containing
+ * =CHOOSE(2,A2,A3,A4)
+ * TODO - support getCellFormula too!
+ */
+ public void test42618() throws Exception {
+ FileInputStream in = new FileInputStream(new File(cwd, "SimpleWithChoose.xls"));
+ HSSFWorkbook wb = new HSSFWorkbook(in);
+ in.close();
+
+ assertTrue("No Exceptions while reading file", true);
+
+ //serialize and read again
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ wb.write(out);
+ out.close();
+
+ wb = new HSSFWorkbook(new ByteArrayInputStream(out.toByteArray()));
+ assertTrue("No Exceptions while reading file", true);
+
+ // Check we detect the string properly too
+ HSSFSheet s = wb.getSheetAt(0);
+
+ // Textual value
+ HSSFRow r1 = s.getRow(0);
+ HSSFCell c1 = r1.getCell((short)1);
+ assertEquals("=CHOOSE(2,A2,A3,A4)", c1.getRichStringCellValue().toString());
+
+ // Formula Value
+ HSSFRow r2 = s.getRow(1);
+ HSSFCell c2 = r2.getCell((short)1);
+ assertEquals(25, (int)c2.getNumericCellValue());
+
+ // This will blow up with a
+ // "EmptyStackException"
+ //assertEquals("=CHOOSE(2,A2,A3,A4)", c2.getCellFormula());
+ }
}
package org.apache.poi.hssf.usermodel;
-import junit.framework.TestCase;
-
-import org.apache.poi.poifs.filesystem.POIFSFileSystem;
-import org.apache.poi.hssf.model.Sheet;
-import org.apache.poi.hssf.record.Record;
-import org.apache.poi.hssf.record.BOFRecord;
-import org.apache.poi.hssf.record.EOFRecord;
-import org.apache.poi.hssf.util.CellReference;
-import org.apache.poi.hssf.util.HSSFColor;
-import org.apache.poi.util.TempFile;
-
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
-
-import java.util.List;
-import java.util.Iterator;
import java.util.Date;
import java.util.GregorianCalendar;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+import org.apache.poi.hssf.model.Sheet;
+import org.apache.poi.hssf.record.HyperlinkRecord;
+import org.apache.poi.hssf.util.HSSFColor;
+import org.apache.poi.poifs.filesystem.POIFSFileSystem;
+import org.apache.poi.util.TempFile;
/**
* Tests various functionity having to do with HSSFCell. For instance support for
assertTrue("Bottom Border", (cs.getBorderBottom() == (short)1));
in.close();
- }
+ }
+
+ /**
+ * Test reading hyperlinks
+ */
+ public void testWithHyperlink() throws Exception {
+ String dir = System.getProperty("HSSF.testdata.path");
+ File f = new File(dir, "WithHyperlink.xls");
+ HSSFWorkbook wb = new HSSFWorkbook(new FileInputStream(f));
+
+ HSSFSheet sheet = wb.getSheetAt(0);
+ HSSFCell cell = sheet.getRow(4).getCell((short)0);
+ HSSFHyperlink link = cell.getHyperlink();
+ assertNotNull(link);
+
+ assertEquals("Foo", link.getLabel());
+ assertEquals("http://poi.apache.org/", link.getAddress());
+ assertEquals(4, link.getRow());
+ assertEquals(0, link.getColumn());
+ }
+
+ /**
+ * Test reading hyperlinks
+ */
+ public void testWithTwoHyperlinks() throws Exception {
+ String dir = System.getProperty("HSSF.testdata.path");
+ File f = new File(dir, "WithTwoHyperLinks.xls");
+ HSSFWorkbook wb = new HSSFWorkbook(new FileInputStream(f));
+
+ HSSFSheet sheet = wb.getSheetAt(0);
+
+ HSSFCell cell1 = sheet.getRow(4).getCell((short)0);
+ HSSFHyperlink link1 = cell1.getHyperlink();
+ assertNotNull(link1);
+ assertEquals("Foo", link1.getLabel());
+ assertEquals("http://poi.apache.org/", link1.getAddress());
+ assertEquals(4, link1.getRow());
+ assertEquals(0, link1.getColumn());
+
+ HSSFCell cell2 = sheet.getRow(8).getCell((short)1);
+ HSSFHyperlink link2 = cell2.getHyperlink();
+ assertNotNull(link2);
+ assertEquals("Bar", link2.getLabel());
+ assertEquals("http://poi.apache.org/", link2.getAddress());
+ assertEquals(8, link2.getRow());
+ assertEquals(1, link2.getColumn());
+
+ }
/*tests the toString() method of HSSFCell*/
public void testToString() throws Exception {
String retrievedPrintArea = workbook.getPrintArea(0);
assertNotNull("Print Area not defined for first sheet", retrievedPrintArea);
- assertEquals(reference, retrievedPrintArea);
+ assertEquals("'" + sheetName + "'!$A$1:$B$1", retrievedPrintArea);
}
String retrievedPrintArea = workbook.getPrintArea(0);
assertNotNull("Print Area not defined for first sheet", retrievedPrintArea);
- assertEquals(sheetName+"!"+reference, retrievedPrintArea);
+ assertEquals("'" + sheetName + "'!" + reference, retrievedPrintArea);
}
String retrievedPrintArea = workbook.getPrintArea(0);
assertNotNull("Print Area not defined for first sheet", retrievedPrintArea);
- assertEquals("References Match", reference, retrievedPrintArea);
+ assertEquals("References Match", "'" + sheetName + "'!$A$1:$B$1", retrievedPrintArea);
}
{
HSSFWorkbook workbook = new HSSFWorkbook();
- HSSFSheet sheet = workbook.createSheet("Sheet 1");
- sheet = workbook.createSheet("Sheet 2");
- sheet = workbook.createSheet("Sheet 3");
+ HSSFSheet sheet = workbook.createSheet("Sheet1");
+ sheet = workbook.createSheet("Sheet2");
+ sheet = workbook.createSheet("Sheet3");
String sheetName = workbook.getSheetName(0);
String reference = null;
String retrievedPrintArea = workbook.getPrintArea(0);
assertNotNull("Print Area not defined for first sheet", retrievedPrintArea);
- assertEquals(reference, retrievedPrintArea);
+ assertEquals("'" + sheetName + "'!$A$1:$B$1", retrievedPrintArea);
}
-
+
+
+ /**
+ * Tests the parsing of union area expressions, and re-display in the presence of sheet names
+ * with special characters.
+ */
+ public void testPrintAreaUnion(){
+ HSSFWorkbook workbook = new HSSFWorkbook();
+ HSSFSheet sheet = workbook.createSheet("Test Print Area");
+ String sheetName = workbook.getSheetName(0);
+
+
+ String reference = sheetName + "!$A$1:$B$1, " + sheetName + "!$D$1:$F$2";
+ String expResult = "'" + sheetName + "'!$A$1:$B$1,'" + sheetName + "'!$D$1:$F$2";
+ workbook.setPrintArea(0, reference);
+
+ String retrievedPrintArea = workbook.getPrintArea(0);
+
+ assertNotNull("Print Area not defined for first sheet", retrievedPrintArea);
+ assertEquals(expResult, retrievedPrintArea);
+ }
+
/**
* Verifies an existing print area is deleted
*