diff options
author | Ugo Cei <ugo@apache.org> | 2008-02-04 16:55:43 +0000 |
---|---|---|
committer | Ugo Cei <ugo@apache.org> | 2008-02-04 16:55:43 +0000 |
commit | 68f4aa5fbcee89eef4be77d7a2a9e518fb29d6dc (patch) | |
tree | ba1e013f2ab72304f05fe197c0eaabfb4ddc7ff5 /src/java | |
parent | 0c5c0e5d0d0474a0f933767832ab2280a88250da (diff) | |
download | poi-68f4aa5fbcee89eef4be77d7a2a9e518fb29d6dc.tar.gz poi-68f4aa5fbcee89eef4be77d7a2a9e518fb29d6dc.zip |
Merged revisions 615190-618235 via svnmerge from
https://svn.apache.org/repos/asf/poi/trunk
........
r615190 | nick | 2008-01-25 12:52:39 +0100 (Fri, 25 Jan 2008) | 1 line
Correctly handle the last paragraph via a fix to TableCell - patch from bug #44292
........
r615255 | nick | 2008-01-25 17:15:49 +0100 (Fri, 25 Jan 2008) | 1 line
Don't swap AreaPtg references from relative to absolute, by correctly processing the fields. Patch from bug #44293
........
r615259 | nick | 2008-01-25 17:33:59 +0100 (Fri, 25 Jan 2008) | 1 line
Add a test to show the bug #42618 appears to be incorrect
........
r615310 | yegor | 2008-01-25 20:27:56 +0100 (Fri, 25 Jan 2008) | 1 line
commented failing test42618()
........
r615315 | yegor | 2008-01-25 20:37:22 +0100 (Fri, 25 Jan 2008) | 1 line
fix bug #44296: HSLF Not Extracting Slide Background Image
........
r615610 | yegor | 2008-01-27 15:55:32 +0100 (Sun, 27 Jan 2008) | 1 line
fix bug #44297: IntPtg must operate with unsigned short. Reading signed short results in incorrect formula calculation.
........
r615769 | yegor | 2008-01-28 09:53:19 +0100 (Mon, 28 Jan 2008) | 1 line
start a new POI 3.1 section in the change log
........
r615859 | nick | 2008-01-28 13:18:12 +0100 (Mon, 28 Jan 2008) | 1 line
Mostly fix bug 42618 (really this time...) - can now open the file properly, but getCellFormula() is still playing up (bug #44306 opened for this)
........
r617156 | nick | 2008-01-31 17:41:53 +0100 (Thu, 31 Jan 2008) | 1 line
Lots of documentation updates, to make it clearer how the code actually works
........
r617167 | nick | 2008-01-31 18:30:16 +0100 (Thu, 31 Jan 2008) | 1 line
Convert HSSFEventFactory to using the new HSSFRecordStream, which returns fully-formed HSSFRecords. HSSFRecordStream allows for pull-style eventusermodel processing
........
r617483 | nick | 2008-02-01 13:13:08 +0100 (Fri, 01 Feb 2008) | 1 line
Tweak the javadoc so it's clearer on the overview what the getFormat method does
........
r617487 | nick | 2008-02-01 13:29:38 +0100 (Fri, 01 Feb 2008) | 1 line
Improvements to how SystemOutLogger and CommonsLogger log messages with exceptions, and avoid an infinite loop with certain log messages with exceptions - triggered by bug #44326
........
r617491 | nick | 2008-02-01 14:02:06 +0100 (Fri, 01 Feb 2008) | 1 line
Patch from bug #44336 - correctly escape sheet names in formula references, including tests for this, and fixes to old tests that were expecting the un-escaped sheet names
........
r617516 | nick | 2008-02-01 16:20:55 +0100 (Fri, 01 Feb 2008) | 1 line
Make a start on the hyperlink record support - not finished yet though, so not enabled
........
r617523 | nick | 2008-02-01 16:41:32 +0100 (Fri, 01 Feb 2008) | 1 line
Get the Hyperlink record code so that it doesn't break any existing tests, and add in (no usermodel support yet though)
........
r617555 | nick | 2008-02-01 17:52:58 +0100 (Fri, 01 Feb 2008) | 1 line
More Hyperlink support. Doesn't end up in HSSFCell just yet, as the records are in the wrong bit of the file, so don't get associated with the sheet. All tests still passing though
........
r617834 | yegor | 2008-02-02 18:06:14 +0100 (Sat, 02 Feb 2008) | 1 line
usermodel support for excel hyperlinks
........
r618230 | nick | 2008-02-04 11:48:29 +0100 (Mon, 04 Feb 2008) | 1 line
Implement CountA, CountIf, Index, Rows and Columns functions. Patch from Josh Micich in bug #44345
........
r618235 | nick | 2008-02-04 12:14:49 +0100 (Mon, 04 Feb 2008) | 1 line
Test file with hyperlinks on many sheets, of different types
........
git-svn-id: https://svn.apache.org/repos/asf/poi/branches/ooxml@618325 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'src/java')
22 files changed, 1220 insertions, 182 deletions
diff --git a/src/java/org/apache/poi/hssf/dev/BiffViewer.java b/src/java/org/apache/poi/hssf/dev/BiffViewer.java index 2e343012d4..242a85f45f 100644 --- a/src/java/org/apache/poi/hssf/dev/BiffViewer.java +++ b/src/java/org/apache/poi/hssf/dev/BiffViewer.java @@ -515,6 +515,9 @@ public class BiffViewer { case FileSharingRecord.sid: retval = new FileSharingRecord( in ); break; + case HyperlinkRecord.sid: + retval = new HyperlinkRecord( in ); + break; default: retval = new UnknownRecord( in ); } diff --git a/src/java/org/apache/poi/hssf/eventusermodel/HSSFEventFactory.java b/src/java/org/apache/poi/hssf/eventusermodel/HSSFEventFactory.java index 70c989c179..f8e64c928e 100644 --- a/src/java/org/apache/poi/hssf/eventusermodel/HSSFEventFactory.java +++ b/src/java/org/apache/poi/hssf/eventusermodel/HSSFEventFactory.java @@ -129,109 +129,25 @@ public class HSSFEventFactory protected short genericProcessEvents(HSSFRequest req, RecordInputStream in) throws IOException, HSSFUserException { + boolean going = true; short userCode = 0; - - short sid = 0; - process: - { - - Record rec = null; - Record lastRec = null; - DrawingRecord lastDrawingRecord = new DrawingRecord(); - - while (in.hasNextRecord()) - { - in.nextRecord(); - sid = in.getSid();; - - // - // for some reasons we have to make the workbook to be at least 4096 bytes - // but if we have such workbook we fill the end of it with zeros (many zeros) - // - // it is not good: - // if the length( all zero records ) % 4 = 1 - // e.g.: any zero record would be readed as 4 bytes at once ( 2 - id and 2 - size ). - // And the last 1 byte will be readed WRONG ( the id must be 2 bytes ) - // - // So we should better to check if the sid is zero and not to read more data - // The zero sid shows us that rest of the stream data is a fake to make workbook - // certain size - // - if ( sid == 0 ) - break; - - - if ((rec != null) && (sid != ContinueRecord.sid)) - { - userCode = req.processRecord(rec); - if (userCode != 0) break process; - } - if (sid != ContinueRecord.sid) - { - //System.out.println("creating "+sid); - Record[] recs = RecordFactory.createRecord(in); - - if (recs.length > 1) - { // we know that the multiple - for (int k = 0; k < (recs.length - 1); k++) - { // record situations do not - userCode = req.processRecord( - recs[ k ]); // contain continue records - if (userCode != 0) break process; - } - } - rec = recs[ recs.length - 1 ]; // regardless we'll process - - // the last record as though - // it might be continued - // if there is only one - // records, it will go here too. - } - else { - // Normally, ContinueRecords are handled internally - // However, in a few cases, there is a gap between a record at - // its Continue, so we have to handle them specially - // This logic is much like in RecordFactory.createRecords() - Record[] recs = RecordFactory.createRecord(in); - ContinueRecord crec = (ContinueRecord)recs[0]; - if((lastRec instanceof ObjRecord) || (lastRec instanceof TextObjectRecord)) { - // You can have Obj records between a DrawingRecord - // and its continue! - lastDrawingRecord.processContinueRecord( crec.getData() ); - // Trigger them on the drawing record, now it's complete - rec = lastDrawingRecord; - } - else if((lastRec instanceof DrawingGroupRecord)) { - ((DrawingGroupRecord)lastRec).processContinueRecord(crec.getData()); - // Trigger them on the drawing record, now it's complete - rec = lastRec; - } - else { - if (rec instanceof UnknownRecord) { - ;//silently skip records we don't know about - } else { - throw new RecordFormatException("Records should handle ContinueRecord internally. Should not see this exception"); - } - } - } - - // Update our tracking of the last record - lastRec = rec; - if(rec instanceof DrawingRecord) { - lastDrawingRecord = (DrawingRecord)rec; - } - } - if (rec != null) - { - userCode = req.processRecord(rec); - if (userCode != 0) break process; + Record r = null; + + // Create a new RecordStream and use that + HSSFRecordStream recordStream = new HSSFRecordStream(in); + + // Process each record as they come in + while(going) { + r = recordStream.nextRecord(); + if(r != null) { + userCode = req.processRecord(r); + if (userCode != 0) break; + } else { + going = false; } } + // All done, return our last code return userCode; - - // Record[] retval = new Record[ records.size() ]; - // retval = ( Record [] ) records.toArray(retval); - // return null; } } diff --git a/src/java/org/apache/poi/hssf/eventusermodel/HSSFRecordStream.java b/src/java/org/apache/poi/hssf/eventusermodel/HSSFRecordStream.java new file mode 100644 index 0000000000..feb7a36d5c --- /dev/null +++ b/src/java/org/apache/poi/hssf/eventusermodel/HSSFRecordStream.java @@ -0,0 +1,234 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ +package org.apache.poi.hssf.eventusermodel; + +import java.util.Vector; + +import org.apache.poi.hssf.record.ContinueRecord; +import org.apache.poi.hssf.record.DrawingGroupRecord; +import org.apache.poi.hssf.record.DrawingRecord; +import org.apache.poi.hssf.record.ObjRecord; +import org.apache.poi.hssf.record.Record; +import org.apache.poi.hssf.record.RecordFactory; +import org.apache.poi.hssf.record.RecordFormatException; +import org.apache.poi.hssf.record.RecordInputStream; +import org.apache.poi.hssf.record.TextObjectRecord; +import org.apache.poi.hssf.record.UnknownRecord; + +/** + * A stream based way to get at complete records, with + * as low a memory footprint as possible. + * This handles reading from a RecordInputStream, turning + * the data into full records, processing continue records + * etc. + * Most users should use {@link HSSFEventFactory} / + * {@link HSSFListener} and have new records pushed to + * them, but this does allow for a "pull" style of coding. + */ +public class HSSFRecordStream { + private RecordInputStream in; + + /** Have we run out of records on the stream? */ + private boolean hitEOS = false; + /** Have we returned all the records there are? */ + private boolean complete = false; + + /** + * Sometimes we end up with a bunch of + * records. When we do, these should + * be returned before the next normal + * record processing occurs (i.e. before + * we check for continue records and + * return rec) + */ + private Vector bonusRecords = null; + + /** + * The next record to return, which may need to have its + * continue records passed to it before we do + */ + private Record rec = null; + /** + * The most recent record that we gave to the user + */ + private Record lastRec = null; + /** + * The most recent DrawingRecord seen + */ + private DrawingRecord lastDrawingRecord = new DrawingRecord(); + + public HSSFRecordStream(RecordInputStream inp) { + this.in = inp; + } + + /** + * Returns the next (complete) record from the + * stream, or null if there are no more. + */ + public Record nextRecord() { + Record r = null; + + // Loop until we get something + while(r == null && !complete) { + // Are there any bonus records that we need to + // return? + r = getBonusRecord(); + + // If not, ask for the next real record + if(r == null) { + r = getNextRecord(); + } + } + + // All done + return r; + } + + /** + * If there are any "bonus" records, that should + * be returned before processing new ones, + * grabs the next and returns it. + * If not, returns null; + */ + private Record getBonusRecord() { + if(bonusRecords != null) { + Record r = (Record)bonusRecords.remove(0); + if(bonusRecords.size() == 0) { + bonusRecords = null; + } + return r; + } + return null; + } + + /** + * Returns the next available record, or null if + * this pass didn't return a record that's + * suitable for returning (eg was a continue record). + */ + private Record getNextRecord() { + Record toReturn = null; + + if(in.hasNextRecord()) { + // Grab our next record + in.nextRecord(); + short sid = in.getSid(); + + // + // for some reasons we have to make the workbook to be at least 4096 bytes + // but if we have such workbook we fill the end of it with zeros (many zeros) + // + // it is not good: + // if the length( all zero records ) % 4 = 1 + // e.g.: any zero record would be readed as 4 bytes at once ( 2 - id and 2 - size ). + // And the last 1 byte will be readed WRONG ( the id must be 2 bytes ) + // + // So we should better to check if the sid is zero and not to read more data + // The zero sid shows us that rest of the stream data is a fake to make workbook + // certain size + // + if ( sid == 0 ) + return null; + + + // If we had a last record, and this one + // isn't a continue record, then pass + // it on to the listener + if ((rec != null) && (sid != ContinueRecord.sid)) + { + // This last record ought to be returned + toReturn = rec; + } + + // If this record isn't a continue record, + // then build it up + if (sid != ContinueRecord.sid) + { + //System.out.println("creating "+sid); + Record[] recs = RecordFactory.createRecord(in); + + // We know that the multiple record situations + // don't contain continue records, so just + // pass those on to the listener now + if (recs.length > 1) { + bonusRecords = new Vector(recs.length-1); + for (int k = 0; k < (recs.length - 1); k++) { + bonusRecords.add(recs[k]); + } + } + + // Regardless of the number we created, always hold + // onto the last record to be processed on the next + // loop, in case it has any continue records + rec = recs[ recs.length - 1 ]; + // Don't return it just yet though, as we probably have + // a record from the last round to return + } + else { + // Normally, ContinueRecords are handled internally + // However, in a few cases, there is a gap between a record at + // its Continue, so we have to handle them specially + // This logic is much like in RecordFactory.createRecords() + Record[] recs = RecordFactory.createRecord(in); + ContinueRecord crec = (ContinueRecord)recs[0]; + if((lastRec instanceof ObjRecord) || (lastRec instanceof TextObjectRecord)) { + // You can have Obj records between a DrawingRecord + // and its continue! + lastDrawingRecord.processContinueRecord( crec.getData() ); + // Trigger them on the drawing record, now it's complete + rec = lastDrawingRecord; + } + else if((lastRec instanceof DrawingGroupRecord)) { + ((DrawingGroupRecord)lastRec).processContinueRecord(crec.getData()); + // Trigger them on the drawing record, now it's complete + rec = lastRec; + } + else { + if (rec instanceof UnknownRecord) { + ;//silently skip records we don't know about + } else { + throw new RecordFormatException("Records should handle ContinueRecord internally. Should not see this exception"); + } + } + } + + // Update our tracking of the last record + lastRec = rec; + if(rec instanceof DrawingRecord) { + lastDrawingRecord = (DrawingRecord)rec; + } + } else { + // No more records + hitEOS = true; + } + + // If we've hit the end-of-stream, then + // finish off the last record and be done + if(hitEOS) { + complete = true; + + // Return the last record if there was + // one, otherwise null + if(rec != null) { + toReturn = rec; + rec = null; + } + } + + return toReturn; + } +}
\ No newline at end of file diff --git a/src/java/org/apache/poi/hssf/model/Workbook.java b/src/java/org/apache/poi/hssf/model/Workbook.java index 08c236cda1..2ba50857ca 100644 --- a/src/java/org/apache/poi/hssf/model/Workbook.java +++ b/src/java/org/apache/poi/hssf/model/Workbook.java @@ -93,6 +93,8 @@ public class Workbook implements Model protected ArrayList formats = new ArrayList(); protected ArrayList names = new ArrayList(); + + protected ArrayList hyperlinks = new ArrayList(); protected int numxfs = 0; // hold the number of extended format records protected int numfonts = 0; // hold the number of font records @@ -133,7 +135,8 @@ public class Workbook implements Model Workbook retval = new Workbook(); ArrayList records = new ArrayList(recs.size() / 3); - for (int k = 0; k < recs.size(); k++) { + int k; + for (k = 0; k < recs.size(); k++) { Record rec = ( Record ) recs.get(k); if (rec.getSid() == EOFRecord.sid) { @@ -248,6 +251,17 @@ public class Workbook implements Model // retval.records.supbookpos = retval.records.bspos + 1; // retval.records.namepos = retval.records.supbookpos + 1; // } + + // Look for other interesting values that + // follow the EOFRecord + for ( ; k < recs.size(); k++) { + Record rec = ( Record ) recs.get(k); + switch (rec.getSid()) { + case HyperlinkRecord.sid: + retval.hyperlinks.add(rec); + break; + } + } retval.records.setRecords(records); @@ -2116,6 +2130,11 @@ public class Workbook implements Model return null; } + public List getHyperlinks() + { + return hyperlinks; + } + public List getRecords() { return records.getRecords(); diff --git a/src/java/org/apache/poi/hssf/record/HyperlinkRecord.java b/src/java/org/apache/poi/hssf/record/HyperlinkRecord.java new file mode 100644 index 0000000000..0dcd45a724 --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/HyperlinkRecord.java @@ -0,0 +1,370 @@ +/* ==================================================================== + Copyright 2002-2004 Apache Software Foundation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + ==================================================================== */ + +package org.apache.poi.hssf.record; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.poi.util.LittleEndian; +import org.apache.poi.util.StringUtil; + +/** + * The <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; + } +} diff --git a/src/java/org/apache/poi/hssf/record/RecordFactory.java b/src/java/org/apache/poi/hssf/record/RecordFactory.java index 20e8ba788a..0f164b4473 100644 --- a/src/java/org/apache/poi/hssf/record/RecordFactory.java +++ b/src/java/org/apache/poi/hssf/record/RecordFactory.java @@ -76,7 +76,8 @@ public class RecordFactory WriteProtectRecord.class, FilePassRecord.class, PaneRecord.class, NoteRecord.class, ObjectProtectRecord.class, ScenarioProtectRecord.class, FileSharingRecord.class, ChartTitleFormatRecord.class, - DVRecord.class, DVALRecord.class, UncalcedRecord.class + DVRecord.class, DVALRecord.class, UncalcedRecord.class, + HyperlinkRecord.class }; } private static Map recordsMap = recordsToMap(records); diff --git a/src/java/org/apache/poi/hssf/record/RecordInputStream.java b/src/java/org/apache/poi/hssf/record/RecordInputStream.java index dd853f2463..32094287fa 100755 --- a/src/java/org/apache/poi/hssf/record/RecordInputStream.java +++ b/src/java/org/apache/poi/hssf/record/RecordInputStream.java @@ -249,7 +249,7 @@ public class RecordInputStream extends InputStream */ public String readUnicodeLEString(int length) { if ((length < 0) || (((remaining() / 2) < length) && !isContinueNext())) { - throw new IllegalArgumentException("Illegal length"); + throw new IllegalArgumentException("Illegal length - asked for " + length + " but only " + (remaining()/2) + " left!"); } StringBuffer buf = new StringBuffer(length); diff --git a/src/java/org/apache/poi/hssf/record/formula/Area3DPtg.java b/src/java/org/apache/poi/hssf/record/formula/Area3DPtg.java index d808a94c4d..ac260ffa4e 100644 --- a/src/java/org/apache/poi/hssf/record/formula/Area3DPtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/Area3DPtg.java @@ -261,13 +261,16 @@ public class Area3DPtg extends Ptg setLastRowRelative( !lastCell.isRowAbsolute() ); } + /** + * @return text representation of this area reference that can be used in text + * formulas. The sheet name will get properly delimited if required. + */ public String toFormulaString(Workbook book) { - SheetReferences refs = book == null ? null : book.getSheetReferences(); StringBuffer retval = new StringBuffer(); - if ( refs != null ) - { - retval.append( refs.getSheetName( this.field_1_index_extern_sheet ) ); + String sheetName = Ref3DPtg.getSheetName(book, field_1_index_extern_sheet); + if(sheetName != null) { + SheetNameFormatter.appendFormat(retval, sheetName); retval.append( '!' ); } retval.append( ( new CellReference( getFirstRow(), getFirstColumn(), !isFirstRowRelative(), !isFirstColRelative() ) ).toString() ); diff --git a/src/java/org/apache/poi/hssf/record/formula/AreaPtg.java b/src/java/org/apache/poi/hssf/record/formula/AreaPtg.java index 32579a69f3..908c8d5e3c 100644 --- a/src/java/org/apache/poi/hssf/record/formula/AreaPtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/AreaPtg.java @@ -43,9 +43,9 @@ public class AreaPtg private short field_3_first_column; private short field_4_last_column; - private BitField rowRelative = BitFieldFactory.getInstance(0x8000); - private BitField colRelative = BitFieldFactory.getInstance(0x4000); - private BitField column = BitFieldFactory.getInstance(0x3FFF); + private final static BitField rowRelative = BitFieldFactory.getInstance(0x8000); + private final static BitField colRelative = BitFieldFactory.getInstance(0x4000); + private final static BitField columnMask = BitFieldFactory.getInstance(0x3FFF); protected AreaPtg() { //Required for clone methods @@ -157,7 +157,7 @@ public class AreaPtg */ public short getFirstColumn() { - return column.getShortValue(field_3_first_column); + return columnMask.getShortValue(field_3_first_column); } /** @@ -204,7 +204,7 @@ public class AreaPtg */ public void setFirstColumn(short column) { - field_3_first_column = column; // fixme + field_3_first_column=columnMask.setShortValue(field_3_first_column, column); } /** @@ -220,7 +220,7 @@ public class AreaPtg */ public short getLastColumn() { - return column.getShortValue(field_4_last_column); + return columnMask.getShortValue(field_4_last_column); } /** @@ -269,7 +269,7 @@ public class AreaPtg */ public void setLastColumn(short column) { - field_4_last_column = column; // fixme + field_4_last_column=columnMask.setShortValue(field_4_last_column, column); } /** diff --git a/src/java/org/apache/poi/hssf/record/formula/ConcatPtg.java b/src/java/org/apache/poi/hssf/record/formula/ConcatPtg.java index b5a2de2a5d..41d2de0cba 100644 --- a/src/java/org/apache/poi/hssf/record/formula/ConcatPtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/ConcatPtg.java @@ -34,11 +34,10 @@ public class ConcatPtg public final static byte sid = 0x08; private final static String CONCAT = "&"; - + public ConcatPtg(RecordInputStream in) { - - // doesn't need anything + // No contents } public ConcatPtg() { diff --git a/src/java/org/apache/poi/hssf/record/formula/IntPtg.java b/src/java/org/apache/poi/hssf/record/formula/IntPtg.java index 602289776b..257c089df8 100644 --- a/src/java/org/apache/poi/hssf/record/formula/IntPtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/IntPtg.java @@ -40,7 +40,7 @@ public class IntPtg { public final static int SIZE = 3; public final static byte sid = 0x1e; - private short field_1_value; + private int field_1_value; private IntPtg() { //Required for clone methods @@ -48,42 +48,31 @@ public class IntPtg public IntPtg(RecordInputStream in) { - setValue(in.readShort()); + setValue(in.readUShort()); } // IntPtg should be able to create itself, shouldnt have to call setValue public IntPtg(String formulaToken) { - setValue(Short.parseShort(formulaToken)); + setValue(Integer.parseInt(formulaToken)); } /** * Sets the wrapped value. * Normally you should call with a positive int. */ - public void setValue(short value) - { - field_1_value = value; - } - - /** - * Sets the unsigned value. - * (Handles conversion to the internal short value) - */ public void setValue(int value) { - if(value > Short.MAX_VALUE) { - // Need to wrap - value -= (Short.MAX_VALUE+1)*2; - } - field_1_value = (short)value; + if(value < 0 || value > (Short.MAX_VALUE + 1)*2 ) + throw new IllegalArgumentException("Unsigned short is out of range: " + value); + field_1_value = value; } /** * Returns the value as a short, which may have * been wrapped into negative numbers */ - public short getValue() + public int getValue() { return field_1_value; } @@ -102,7 +91,7 @@ public class IntPtg public void writeBytes(byte [] array, int offset) { array[ offset + 0 ] = sid; - LittleEndian.putShort(array, offset + 1, getValue()); + LittleEndian.putUShort(array, offset + 1, getValue()); } public int getSize() diff --git a/src/java/org/apache/poi/hssf/record/formula/Ptg.java b/src/java/org/apache/poi/hssf/record/formula/Ptg.java index cc9a4236ba..f006509c23 100644 --- a/src/java/org/apache/poi/hssf/record/formula/Ptg.java +++ b/src/java/org/apache/poi/hssf/record/formula/Ptg.java @@ -137,8 +137,8 @@ public abstract class Ptg break; case DividePtg.sid : // 0x06 - retval = new DividePtg(in); - break; + retval = new DividePtg(in); + break; case PowerPtg.sid : // 0x07 retval = new PowerPtg(in); @@ -208,6 +208,7 @@ public abstract class Ptg break; case AttrPtg.sid : // 0x19 + case 0x1a : retval = new AttrPtg(in); break; @@ -224,8 +225,8 @@ public abstract class Ptg break; case NumberPtg.sid : // 0x1f - retval = new NumberPtg(in); - break; + retval = new NumberPtg(in); + break; case ArrayPtg.sid : // 0x20 retval = new ArrayPtg(in); @@ -350,9 +351,12 @@ public abstract class Ptg case DeletedArea3DPtg.sid + 0x40 : // 0x7d retval = new DeletedArea3DPtg(in); break; - + + case 0x00: + retval = new UnknownPtg(); + break; + default : - //retval = new UnknownPtg(); throw new java.lang.UnsupportedOperationException(" Unknown Ptg in Formula: 0x"+ Integer.toHexString(( int ) id) + " (" + ( int ) id + ")"); diff --git a/src/java/org/apache/poi/hssf/record/formula/RangePtg.java b/src/java/org/apache/poi/hssf/record/formula/RangePtg.java index f0bd8c1c1b..51df7844a4 100644 --- a/src/java/org/apache/poi/hssf/record/formula/RangePtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/RangePtg.java @@ -25,22 +25,22 @@ import org.apache.poi.hssf.record.RecordInputStream; */ public class RangePtg extends OperationPtg { + public final static int SIZE = 1; public final static byte sid = 0x11; - public RangePtg() { } public RangePtg(RecordInputStream in) { - // doesn't need anything + // No contents } public int getSize() { - return 1; + return SIZE; } public void writeBytes( byte[] array, int offset ) diff --git a/src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java b/src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java index bc5430bb5d..510eebb037 100644 --- a/src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java @@ -157,18 +157,31 @@ public class Ref3DPtg extends Ptg { } - public String toFormulaString(Workbook book) { + // TODO - find a home for this method + // There is already a method on Workbook called getSheetName but it seems to do something different. + static String getSheetName(Workbook book, int externSheetIndex) { + // TODO - there are 3 ways this method can return null. Is each valid? + if (book == null) { + return null; + } + + SheetReferences refs = book.getSheetReferences(); + if (refs == null) { + return null; + } + return refs.getSheetName(externSheetIndex); + } + /** + * @return text representation of this cell reference that can be used in text + * formulas. The sheet name will get properly delimited if required. + */ + public String toFormulaString(Workbook book) + { StringBuffer retval = new StringBuffer(); - SheetReferences refs = book == null ? null : book.getSheetReferences(); - if (refs != null) { - String sheetName =refs.getSheetName((int)this.field_1_index_extern_sheet); - boolean appendQuotes = sheetName.indexOf(" ") >= 0; - if (appendQuotes) - retval.append("'"); - retval.append(sheetName); - if (appendQuotes) - retval.append("'"); - retval.append('!'); + String sheetName = getSheetName(book, field_1_index_extern_sheet); + if(sheetName != null) { + SheetNameFormatter.appendFormat(retval, sheetName); + retval.append( '!' ); } retval.append((new CellReference(getRow(),getColumn(),!isRowRelative(),!isColRelative())).toString()); return retval.toString(); diff --git a/src/java/org/apache/poi/hssf/record/formula/SheetNameFormatter.java b/src/java/org/apache/poi/hssf/record/formula/SheetNameFormatter.java new file mode 100755 index 0000000000..ba796db3b2 --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/formula/SheetNameFormatter.java @@ -0,0 +1,245 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + + +package org.apache.poi.hssf.record.formula; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Formats sheet names for use in formula expressions. + * + * @author Josh Micich + */ +final class SheetNameFormatter { + + private static final String BIFF8_LAST_COLUMN = "IV"; + private static final int BIFF8_LAST_COLUMN_TEXT_LEN = BIFF8_LAST_COLUMN.length(); + private static final String BIFF8_LAST_ROW = String.valueOf(0x10000); + private static final int BIFF8_LAST_ROW_TEXT_LEN = BIFF8_LAST_ROW.length(); + + private static final char DELIMITER = '\''; + + private static final Pattern CELL_REF_PATTERN = Pattern.compile("([A-Za-z])+[0-9]+"); + + private SheetNameFormatter() { + // no instances of this class + } + /** + * Used to format sheet names as they would appear in cell formula expressions. + * @return the sheet name unchanged if there is no need for delimiting. Otherwise the sheet + * name is enclosed in single quotes ('). Any single quotes which were already present in the + * sheet name will be converted to double single quotes (''). + */ + public static String format(String rawSheetName) { + StringBuffer sb = new StringBuffer(rawSheetName.length() + 2); + appendFormat(sb, rawSheetName); + return sb.toString(); + } + + /** + * Convenience method for when a StringBuffer is already available + * + * @param out - sheet name will be appended here possibly with delimiting quotes + */ + public static void appendFormat(StringBuffer out, String rawSheetName) { + boolean needsQuotes = needsDelimiting(rawSheetName); + if(needsQuotes) { + out.append(DELIMITER); + appendAndEscape(out, rawSheetName); + out.append(DELIMITER); + } else { + out.append(rawSheetName); + } + } + + private static void appendAndEscape(StringBuffer sb, String rawSheetName) { + int len = rawSheetName.length(); + for(int i=0; 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()); + } + +} diff --git a/src/java/org/apache/poi/hssf/record/formula/UnknownPtg.java b/src/java/org/apache/poi/hssf/record/formula/UnknownPtg.java index 9cc5158325..1badf51970 100644 --- a/src/java/org/apache/poi/hssf/record/formula/UnknownPtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/UnknownPtg.java @@ -28,7 +28,7 @@ import org.apache.poi.hssf.record.RecordInputStream; public class UnknownPtg extends Ptg { - private short size; + private short size = 1; /** Creates new UnknownPtg */ diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java b/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java index 33417dad7f..19ead33f0e 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java @@ -34,20 +34,7 @@ import java.util.Iterator; import org.apache.poi.hssf.model.FormulaParser; import org.apache.poi.hssf.model.Sheet; import org.apache.poi.hssf.model.Workbook; -import org.apache.poi.hssf.record.BlankRecord; -import org.apache.poi.hssf.record.BoolErrRecord; -import org.apache.poi.hssf.record.CellValueRecordInterface; -import org.apache.poi.hssf.record.CommonObjectDataSubRecord; -import org.apache.poi.hssf.record.ExtendedFormatRecord; -import org.apache.poi.hssf.record.FormulaRecord; -import org.apache.poi.hssf.record.LabelSSTRecord; -import org.apache.poi.hssf.record.NoteRecord; -import org.apache.poi.hssf.record.NumberRecord; -import org.apache.poi.hssf.record.ObjRecord; -import org.apache.poi.hssf.record.Record; -import org.apache.poi.hssf.record.SubRecord; -import org.apache.poi.hssf.record.TextObjectRecord; -import org.apache.poi.hssf.record.UnicodeString; +import org.apache.poi.hssf.record.*; import org.apache.poi.hssf.record.aggregates.FormulaRecordAggregate; import org.apache.poi.hssf.record.formula.Ptg; import org.apache.poi.ss.usermodel.Cell; @@ -1073,4 +1060,31 @@ public class HSSFCell implements Cell } return comment; } + + /** + * Returns hyperlink associated with this cell + * + * @return hyperlink associated with this cell or null if not found + */ + public HSSFHyperlink getHyperlink(){ + for (Iterator it = sheet.getRecords().iterator(); it.hasNext(); ) { + Record rec = ( Record ) it.next(); + if (rec instanceof HyperlinkRecord){ + HyperlinkRecord link = (HyperlinkRecord)rec; + if(link.getColumn() == record.getColumn() && link.getRow() == record.getRow()){ + return new HSSFHyperlink(link); + } + } + } + return null; + } + + /** + * Assign a hypelrink to this cell + * + * @param link hypelrink associated with this cell + */ + public void setHyperlink(HSSFHyperlink link){ + + } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFDataFormat.java b/src/java/org/apache/poi/hssf/usermodel/HSSFDataFormat.java index 547ec83038..fb7ac49f39 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFDataFormat.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFDataFormat.java @@ -206,12 +206,12 @@ public class HSSFDataFormat implements DataFormat } /** - * get the format index that matches the given format string. - * Creates a new format if one is not found. Aliases text to the proper format. + * Get the format index that matches the given format + * string, creating a new format entry if required. + * Aliases text to the proper format as required. * @param format string matching a built in format * @return index of format. */ - public short getFormat( String format ) { ListIterator i; diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFHyperlink.java b/src/java/org/apache/poi/hssf/usermodel/HSSFHyperlink.java new file mode 100755 index 0000000000..e1bd28af6c --- /dev/null +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFHyperlink.java @@ -0,0 +1,128 @@ +/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.hssf.usermodel;
+
+import org.apache.poi.hssf.record.EscherAggregate;
+import org.apache.poi.hssf.record.NoteRecord;
+import org.apache.poi.hssf.record.TextObjectRecord;
+import org.apache.poi.hssf.record.HyperlinkRecord;
+import org.apache.poi.ddf.*;
+
+import java.util.Map;
+import java.util.List;
+import java.util.Iterator;
+
+/**
+ * Represents a hyperlink.
+ *
+ * @author Yegor Kozlov
+ */
+public class HSSFHyperlink {
+
+ /**
+ * Link to a existing file or web page
+ */
+ public static final int LINK_URL = 1;
+
+ /**
+ * Link to a place in this document
+ */
+ public static final int LINK_DOCUMENT = 2;
+
+ /**
+ * Link to an E-mail address
+ */
+ public static final int LINK_EMAIL = 3;
+
+ /**
+ * Unknown type
+ */
+ public static final int LINK_UNKNOWN = 4;
+
+ /**
+ * Low-level record object that stores the actual hyperlink data
+ */
+ private HyperlinkRecord record = null;
+
+ protected HSSFHyperlink( HyperlinkRecord record )
+ {
+ this.record = record;
+ }
+
+ /**
+ * Return the row of the cell that contains the hyperlink
+ *
+ * @return the 0-based row of the cell that contains the hyperlink
+ */
+ public int getRow(){
+ return record.getRow();
+ }
+
+ /**
+ * Set the row of the cell that contains the hyperlink
+ *
+ * @param row the 0-based row of the cell that contains the hyperlink
+ */
+ public void setRow(int row){
+ record.setRow(row);
+ }
+
+ /**
+ * Return the column of the cell that contains the hyperlink
+ *
+ * @return the 0-based column of the cell that contains the hyperlink
+ */
+ public short getColumn(){
+ return record.getColumn();
+ }
+
+ /**
+ * Set the column of the cell that contains the hyperlink
+ *
+ * @param col the 0-based column of the cell that contains the hyperlink
+ */
+ public void setColumn(short col){
+ record.setColumn(col);
+ }
+
+ /**
+ * Hypelink address. Depending on the hyperlink type it can be URL, e-mail, etc.
+ *
+ * @return the address of this hyperlink
+ */
+ public String getAddress(){
+ return record.getUrlString();
+ }
+
+ /**
+ * Return text to display for this hyperlink
+ *
+ * @return text to display
+ */
+ public String getLabel(){
+ return record.getLabel();
+ }
+
+ /**
+ * Return the type of this hyperlink
+ *
+ * @return the type of this hyperlink
+ */
+ public int getType(){
+ throw new RuntimeException("Not implemented");
+ }
+}
diff --git a/src/java/org/apache/poi/util/CommonsLogger.java b/src/java/org/apache/poi/util/CommonsLogger.java index 4265a88af8..16a48f28f6 100644 --- a/src/java/org/apache/poi/util/CommonsLogger.java +++ b/src/java/org/apache/poi/util/CommonsLogger.java @@ -22,8 +22,6 @@ package org.apache.poi.util; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import java.util.*; - /** * A logger class that strives to make it as easy as possible for * developers to write log calls, while simultaneously making those @@ -53,7 +51,6 @@ public class CommonsLogger extends POILogger * @param level One of DEBUG, INFO, WARN, ERROR, FATAL * @param obj1 The object to log. */ - public void log(final int level, final Object obj1) { if(level==FATAL) @@ -98,6 +95,78 @@ public class CommonsLogger extends POILogger log.trace(obj1); } } + } + + /** + * Log a message + * + * @param level One of DEBUG, INFO, WARN, ERROR, FATAL + * @param obj1 The object to log. This is converted to a string. + * @param exception An exception to be logged + */ + public void log(final int level, final Object obj1, + final Throwable exception) + { + if(level==FATAL) + { + if(log.isFatalEnabled()) + { + if(obj1 != null) + log.fatal(obj1, exception); + else + log.fatal(exception); + } + } + else if(level==ERROR) + { + if(log.isErrorEnabled()) + { + if(obj1 != null) + log.error(obj1, exception); + else + log.error(exception); + } + } + else if(level==WARN) + { + if(log.isWarnEnabled()) + { + if(obj1 != null) + log.warn(obj1, exception); + else + log.warn(exception); + } + } + else if(level==INFO) + { + if(log.isInfoEnabled()) + { + if(obj1 != null) + log.info(obj1, exception); + else + log.info(exception); + } + } + else if(level==DEBUG) + { + if(log.isDebugEnabled()) + { + if(obj1 != null) + log.debug(obj1, exception); + else + log.debug(exception); + } + } + else + { + if(log.isTraceEnabled()) + { + if(obj1 != null) + log.trace(obj1, exception); + else + log.trace(exception); + } + } } diff --git a/src/java/org/apache/poi/util/POILogger.java b/src/java/org/apache/poi/util/POILogger.java index b2d358d4d8..514edf90ca 100644 --- a/src/java/org/apache/poi/util/POILogger.java +++ b/src/java/org/apache/poi/util/POILogger.java @@ -51,7 +51,24 @@ public abstract class POILogger abstract public void initialize(final String cat); + /** + * Log a message + * + * @param level One of DEBUG, INFO, WARN, ERROR, FATAL + * @param obj1 The object to log. This is converted to a string. + */ abstract public void log(final int level, final Object obj1); + + /** + * Log a message + * + * @param level One of DEBUG, INFO, WARN, ERROR, FATAL + * @param obj1 The object to log. This is converted to a string. + * @param exception An exception to be logged + */ + abstract public void log(final int level, final Object obj1, + final Throwable exception); + /** * Check if a logger is enabled to log at the specified level @@ -237,17 +254,15 @@ public abstract class POILogger } /** - * Log a message + * Log an exception, without a message * * @param level One of DEBUG, INFO, WARN, ERROR, FATAL - * @param obj1 The object to log. This is converted to a string. * @param exception An exception to be logged */ - public void log(final int level, final Object obj1, - final Throwable exception) + public void log(final int level, final Throwable exception) { - log(level , obj1, exception); + log(level, null, exception); } /** diff --git a/src/java/org/apache/poi/util/SystemOutLogger.java b/src/java/org/apache/poi/util/SystemOutLogger.java index 8b3dc50d9a..af678e186a 100644 --- a/src/java/org/apache/poi/util/SystemOutLogger.java +++ b/src/java/org/apache/poi/util/SystemOutLogger.java @@ -49,8 +49,24 @@ public class SystemOutLogger extends POILogger public void log(final int level, final Object obj1) { - if (check(level)) + log(level, obj1, null); + } + + /** + * Log a message + * + * @param level One of DEBUG, INFO, WARN, ERROR, FATAL + * @param obj1 The object to log. This is converted to a string. + * @param exception An exception to be logged + */ + public void log(final int level, final Object obj1, + final Throwable exception) { + if (check(level)) { System.out.println("["+cat+"] "+obj1); + if(exception != null) { + exception.printStackTrace(System.out); + } + } } /** |