diff --git a/src/documentation/content/xdocs/changes.xml b/src/documentation/content/xdocs/changes.xml
index 36f01ceea5..c0f06eb8bd 100644
--- a/src/documentation/content/xdocs/changes.xml
+++ b/src/documentation/content/xdocs/changes.xml
@@ -67,6 +67,9 @@
Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx
+ 45912 - fixed ArrayIndexOutOfBoundsException in EmbeddedObjectRefSubRecord
+ 45889 - fixed ArrayIndexOutOfBoundsException when constructing HSLF Table with a single row
+ Initial support for creating hyperlinks in HSLF
45876 - fixed BoundSheetRecord to allow sheet names longer than 31 chars
45890 - fixed HSSFSheet.shiftRows to also update conditional formats
45865 modified Formula Parser/Evaluator to handle cross-worksheet formulas
diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml
index bcf430664c..9a4b02f57f 100644
--- a/src/documentation/content/xdocs/status.xml
+++ b/src/documentation/content/xdocs/status.xml
@@ -64,6 +64,9 @@
Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx
+ 45912 - fixed ArrayIndexOutOfBoundsException in EmbeddedObjectRefSubRecord
+ 45889 - fixed ArrayIndexOutOfBoundsException when constructing HSLF Table with a single row
+ Initial support for creating hyperlinks in HSLF
45876 - fixed BoundSheetRecord to allow sheet names longer than 31 chars
45890 - fixed HSSFSheet.shiftRows to also update conditional formats
45865 modified Formula Parser/Evaluator to handle cross-worksheet formulas
diff --git a/src/java/org/apache/poi/hssf/record/EmbeddedObjectRefSubRecord.java b/src/java/org/apache/poi/hssf/record/EmbeddedObjectRefSubRecord.java
index fb6bce5d3a..9e8d998b11 100644
--- a/src/java/org/apache/poi/hssf/record/EmbeddedObjectRefSubRecord.java
+++ b/src/java/org/apache/poi/hssf/record/EmbeddedObjectRefSubRecord.java
@@ -1,4 +1,3 @@
-
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
@@ -16,166 +15,312 @@
limitations under the License.
==================================================================== */
-
package org.apache.poi.hssf.record;
+import java.io.ByteArrayInputStream;
-
-import org.apache.poi.util.*;
+import org.apache.poi.hssf.record.formula.Area3DPtg;
+import org.apache.poi.hssf.record.formula.AreaPtg;
+import org.apache.poi.hssf.record.formula.Ptg;
+import org.apache.poi.hssf.record.formula.Ref3DPtg;
+import org.apache.poi.hssf.record.formula.RefPtg;
+import org.apache.poi.util.HexDump;
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.StringUtil;
/**
+ * ftPictFmla (0x0009)
* A sub-record within the OBJ record which stores a reference to an object
* stored in a separate entry within the OLE2 compound file.
*
* @author Daniel Noll
*/
-public class EmbeddedObjectRefSubRecord
- extends SubRecord
-{
- public static final short sid = 0x9;
+public final class EmbeddedObjectRefSubRecord extends SubRecord {
+ public static final short sid = 0x0009;
- public short field_1_stream_id_offset; // Offset to stream ID from the point after this value.
- public short[] field_2_unknown; // Unknown stuff at the front. TODO: Confirm that it's a short[]
- // TODO: Consider making a utility class for these. I've discovered the same field ordering
- // in FormatRecord and StringRecord, it may be elsewhere too.
- public short field_3_unicode_len; // Length of Unicode string.
- public boolean field_4_unicode_flag; // Flags whether the string is Unicode.
- public String field_5_ole_classname; // Classname of the embedded OLE document (e.g. Word.Document.8)
- public int field_6_stream_id; // ID of the OLE stream containing the actual data.
+ private static final byte[] EMPTY_BYTE_ARRAY = { };
- private int field_5_ole_classname_padding; // developer laziness...
- public byte[] remainingBytes;
+ private int field_1_unknown_int;
+ /** either an area or a cell ref */
+ private Ptg field_2_refPtg;
+ private byte[] field_2_unknownFormulaData;
+ // TODO: Consider making a utility class for these. I've discovered the same field ordering
+ // in FormatRecord and StringRecord, it may be elsewhere too.
+ private boolean field_3_unicode_flag; // Flags whether the string is Unicode.
+ private String field_4_ole_classname; // Classname of the embedded OLE document (e.g. Word.Document.8)
+ /** Formulas often have a single non-zero trailing byte.
+ * This is in a similar position to he pre-streamId padding
+ * It is unknown if the value is important (it seems to mirror a value a few bytes earlier)
+ * */
+ private Byte field_4_unknownByte;
+ private Integer field_5_stream_id; // ID of the OLE stream containing the actual data.
+ private byte[] field_6_unknown;
- public EmbeddedObjectRefSubRecord()
- {
- field_2_unknown = new short[0];
- remainingBytes = new byte[0];
- field_1_stream_id_offset = 6;
- field_5_ole_classname = "";
- }
- public short getSid()
- {
- return sid;
- }
+ // currently for testing only - needs review
+ EmbeddedObjectRefSubRecord() {
+ field_2_unknownFormulaData = new byte[] { 0x02, 0x6C, 0x6A, 0x16, 0x01, }; // just some sample data. These values vary a lot
+ field_6_unknown = EMPTY_BYTE_ARRAY;
+ field_4_ole_classname = null;
+ }
- public EmbeddedObjectRefSubRecord(RecordInputStream in)
- {
- field_1_stream_id_offset = in.readShort();
- field_2_unknown = in.readShortArray();
- field_3_unicode_len = in.readShort();
- field_4_unicode_flag = ( in.readByte() & 0x01 ) != 0;
+ public short getSid() {
+ return sid;
+ }
- if ( field_4_unicode_flag )
- {
- field_5_ole_classname = in.readUnicodeLEString( field_3_unicode_len );
- }
- else
- {
- field_5_ole_classname = in.readCompressedUnicode( field_3_unicode_len );
- }
+ public EmbeddedObjectRefSubRecord(RecordInputStream in) {
+ // Much guess-work going on here due to lack of any documentation.
+ // See similar source code in OOO:
+ // http://lxr.go-oo.org/source/sc/sc/source/filter/excel/xiescher.cxx
+ // 1223 void XclImpOleObj::ReadPictFmla( XclImpStream& rStrm, sal_uInt16 nRecSize )
- // Padded with NUL bytes. The -2 is because field_1_stream_id_offset
- // is relative to after the offset field, whereas in.getRecordOffset()
- // is relative to the start of this record (minus the header.)
- field_5_ole_classname_padding = 0;
- while (in.getRecordOffset() - 2 < field_1_stream_id_offset)
- {
- field_5_ole_classname_padding++;
- in.readByte(); // discard
- }
+ int streamIdOffset = in.readShort(); // OOO calls this 'nFmlaLen'
- // Fetch the stream ID
- field_6_stream_id = in.readInt();
-
- // Store what's left
- remainingBytes = in.readRemainder();
- }
+ int dataLenAfterFormula = in.remaining() - streamIdOffset;
+ int formulaSize = in.readUShort();
+ field_1_unknown_int = in.readInt();
+ byte[] formulaRawBytes = readRawData(in, formulaSize);
+ field_2_refPtg = readRefPtg(formulaRawBytes);
+ if (field_2_refPtg == null) {
+ // common case
+ // field_2_n16 seems to be 5 here
+ // The formula almost looks like tTbl but the row/column values seem like garbage.
+ field_2_unknownFormulaData = formulaRawBytes;
+ } else {
+ field_2_unknownFormulaData = null;
+ }
- public int serialize(int offset, byte[] data)
- {
- int pos = offset;
+ int stringByteCount;
+ if (in.remaining() >= dataLenAfterFormula + 3) {
+ int tag = in.readByte();
+ if (tag != 0x03) {
+ throw new RecordFormatException("Expected byte 0x03 here");
+ }
+ int nChars = in.readUShort();
+ if (nChars > 0) {
+ // OOO: the 4th way Xcl stores a unicode string: not even a Grbit byte present if length 0
+ field_3_unicode_flag = ( in.readByte() & 0x01 ) != 0;
+ if (field_3_unicode_flag) {
+ field_4_ole_classname = in.readUnicodeLEString(nChars);
+ stringByteCount = nChars * 2;
+ } else {
+ field_4_ole_classname = in.readCompressedUnicode(nChars);
+ stringByteCount = nChars;
+ }
+ } else {
+ field_4_ole_classname = "";
+ stringByteCount = 0;
+ }
+ } else {
+ field_4_ole_classname = null;
+ stringByteCount = 0;
+ }
+ // Pad to next 2-byte boundary
+ if (((stringByteCount + formulaSize) % 2) != 0) {
+ int b = in.readByte();
+ if (field_2_refPtg != null && field_4_ole_classname == null) {
+ field_4_unknownByte = new Byte((byte)b);
+ }
+ }
+ int nUnexpectedPadding = in.remaining() - dataLenAfterFormula;
- LittleEndian.putShort(data, pos, sid); pos += 2;
- LittleEndian.putShort(data, pos, (short)(getRecordSize() - 4)); pos += 2;
+ if (nUnexpectedPadding > 0) {
+ System.err.println("Discarding " + nUnexpectedPadding + " unexpected padding bytes ");
+ readRawData(in, nUnexpectedPadding);
+ }
- LittleEndian.putShort(data, pos, field_1_stream_id_offset); pos += 2;
- LittleEndian.putShortArray(data, pos, field_2_unknown); pos += field_2_unknown.length * 2 + 2;
- LittleEndian.putShort(data, pos, field_3_unicode_len); pos += 2;
- data[pos] = field_4_unicode_flag ? (byte) 0x01 : (byte) 0x00; pos++;
+ // Fetch the stream ID
+ if (dataLenAfterFormula >= 4) {
+ field_5_stream_id = new Integer(in.readInt());
+ } else {
+ field_5_stream_id = null;
+ }
- if ( field_4_unicode_flag )
- {
- StringUtil.putUnicodeLE( field_5_ole_classname, data, pos ); pos += field_5_ole_classname.length() * 2;
- }
- else
- {
- StringUtil.putCompressedUnicode( field_5_ole_classname, data, pos ); pos += field_5_ole_classname.length();
- }
+ field_6_unknown = in.readRemainder();
+ }
- // Padded with the same number of NUL bytes as were originally skipped.
- // XXX: This is only accurate until we make the classname mutable.
- pos += field_5_ole_classname_padding;
-
- LittleEndian.putInt(data, pos, field_6_stream_id); pos += 4;
+ private static Ptg readRefPtg(byte[] formulaRawBytes) {
+ byte[] data = new byte[formulaRawBytes.length + 4];
+ LittleEndian.putUShort(data, 0, -5555);
+ LittleEndian.putUShort(data, 2, formulaRawBytes.length);
+ System.arraycopy(formulaRawBytes, 0, data, 4, formulaRawBytes.length);
+ RecordInputStream in = new RecordInputStream(new ByteArrayInputStream(data));
+ in.nextRecord();
+ byte ptgSid = in.readByte();
+ switch(ptgSid) {
+ case AreaPtg.sid: return new AreaPtg(in);
+ case Area3DPtg.sid: return new Area3DPtg(in);
+ case RefPtg.sid: return new RefPtg(in);
+ case Ref3DPtg.sid: return new Ref3DPtg(in);
+ }
+ return null;
+ }
- System.arraycopy(remainingBytes, 0, data, pos, remainingBytes.length);
+ private static byte[] readRawData(RecordInputStream in, int size) {
+ if (size < 0) {
+ throw new IllegalArgumentException("Negative size (" + size + ")");
+ }
+ if (size == 0) {
+ return EMPTY_BYTE_ARRAY;
+ }
+ byte[] result = new byte[size];
+ for(int i=0; i< size; i++) {
+ result[i] = in.readByte();
+ }
+ return result;
+ }
+
+ private int getStreamIDOffset(int formulaSize) {
+ int result = 2 + 4; // formulaSize + f2unknown_int
+ result += formulaSize;
+
+ int stringLen;
+ if (field_4_ole_classname == null) {
+ // don't write 0x03, stringLen, flag, text
+ stringLen = 0;
+ } else {
+ result += 1 + 2 + 1; // 0x03, stringLen, flag
+ stringLen = field_4_ole_classname.length();
+ if (field_3_unicode_flag) {
+ result += stringLen * 2;
+ } else {
+ result += stringLen;
+ }
+ }
+ // pad to next 2 byte boundary
+ if ((result % 2) != 0) {
+ result ++;
+ }
+ return result;
+ }
+
+ private int getDataSize(int idOffset) {
- return getRecordSize();
- }
+ int result = 2 + idOffset; // 2 for idOffset short field itself
+ if (field_5_stream_id != null) {
+ result += 4;
+ }
+ return result + field_6_unknown.length;
+ }
+ private int getDataSize() {
+ int formulaSize = field_2_refPtg == null ? field_2_unknownFormulaData.length : field_2_refPtg.getSize();
+ int idOffset = getStreamIDOffset(formulaSize);
+ return getDataSize(idOffset);
+ }
- /**
- * Size of record (exluding 4 byte header)
- */
- public int getRecordSize()
- {
- // The stream id offset is relative to after the stream ID.
- // Add 2 bytes for the stream id offset and 4 bytes for the stream id itself and 4 byts for the record header.
- return remainingBytes.length + field_1_stream_id_offset + 2 + 4 + 4;
- }
+ public int serialize(int base, byte[] data) {
- /**
- * Gets the stream ID containing the actual data. The data itself
- * can be found under a top-level directory entry in the OLE2 filesystem
- * under the name "MBDxxxxxxxx" where xxxxxxxx is
- * this ID converted into hex (in big endian order, funnily enough.)
- *
- * @return the data stream ID.
- */
- public int getStreamId()
- {
- return field_6_stream_id;
- }
+ int formulaSize = field_2_refPtg == null ? field_2_unknownFormulaData.length : field_2_refPtg.getSize();
+ int idOffset = getStreamIDOffset(formulaSize);
+ int dataSize = getDataSize(idOffset);
+
- public String toString()
- {
- StringBuffer buffer = new StringBuffer();
- buffer.append("[ftPictFmla]\n");
- buffer.append(" .streamIdOffset = ")
- .append("0x").append(HexDump.toHex( field_1_stream_id_offset ))
- .append(" (").append( field_1_stream_id_offset ).append(" )")
- .append(System.getProperty("line.separator"));
- buffer.append(" .unknown = ")
- .append("0x").append(HexDump.toHex( field_2_unknown ))
- .append(" (").append( field_2_unknown.length ).append(" )")
- .append(System.getProperty("line.separator"));
- buffer.append(" .unicodeLen = ")
- .append("0x").append(HexDump.toHex( field_3_unicode_len ))
- .append(" (").append( field_3_unicode_len ).append(" )")
- .append(System.getProperty("line.separator"));
- buffer.append(" .unicodeFlag = ")
- .append("0x").append( field_4_unicode_flag ? 0x01 : 0x00 )
- .append(" (").append( field_4_unicode_flag ).append(" )")
- .append(System.getProperty("line.separator"));
- buffer.append(" .oleClassname = ")
- .append(field_5_ole_classname)
- .append(System.getProperty("line.separator"));
- buffer.append(" .streamId = ")
- .append("0x").append(HexDump.toHex( field_6_stream_id ))
- .append(" (").append( field_6_stream_id ).append(" )")
- .append(System.getProperty("line.separator"));
- buffer.append("[/ftPictFmla]");
- return buffer.toString();
- }
+ LittleEndian.putUShort(data, base + 0, sid);
+ LittleEndian.putUShort(data, base + 2, dataSize);
+ LittleEndian.putUShort(data, base + 4, idOffset);
+ LittleEndian.putUShort(data, base + 6, formulaSize);
+ LittleEndian.putInt(data, base + 8, field_1_unknown_int);
+
+ int pos = base+12;
+
+ if (field_2_refPtg == null) {
+ System.arraycopy(field_2_unknownFormulaData, 0, data, pos, field_2_unknownFormulaData.length);
+ } else {
+ field_2_refPtg.writeBytes(data, pos);
+ }
+ pos += formulaSize;
+
+ int stringLen;
+ if (field_4_ole_classname == null) {
+ // don't write 0x03, stringLen, flag, text
+ stringLen = 0;
+ } else {
+ LittleEndian.putByte(data, pos, 0x03);
+ pos += 1;
+ stringLen = field_4_ole_classname.length();
+ LittleEndian.putUShort(data, pos, stringLen);
+ pos += 2;
+ LittleEndian.putByte(data, pos, field_3_unicode_flag ? 0x01 : 0x00);
+ pos += 1;
+
+ if (field_3_unicode_flag) {
+ StringUtil.putUnicodeLE(field_4_ole_classname, data, pos);
+ pos += stringLen * 2;
+ } else {
+ StringUtil.putCompressedUnicode(field_4_ole_classname, data, pos);
+ pos += stringLen;
+ }
+ }
+
+ // pad to next 2-byte boundary (requires 0 or 1 bytes)
+ switch(idOffset - (pos - 6 - base)) { // 6 for 3 shorts: sid, dataSize, idOffset
+ case 1:
+ LittleEndian.putByte(data, pos, field_4_unknownByte == null ? 0x00 : field_4_unknownByte.intValue());
+ pos ++;
+ case 0:
+ break;
+ default:
+ throw new IllegalStateException("Bad padding calculation (" + idOffset + ", " + (pos-base) + ")");
+ }
+
+ if (field_5_stream_id != null) {
+ LittleEndian.putInt(data, pos, field_5_stream_id.intValue());
+ pos += 4;
+ }
+ System.arraycopy(field_6_unknown, 0, data, pos, field_6_unknown.length);
+
+ return 4 + dataSize;
+ }
+
+ public int getRecordSize() {
+ return 4 + getDataSize();
+ }
+
+ /**
+ * Gets the stream ID containing the actual data. The data itself
+ * can be found under a top-level directory entry in the OLE2 filesystem
+ * under the name "MBDxxxxxxxx" where xxxxxxxx is
+ * this ID converted into hex (in big endian order, funnily enough.)
+ *
+ * @return the data stream ID. Possibly null
+ */
+ public Integer getStreamId() {
+ return field_5_stream_id;
+ }
+
+ public String getOLEClassName() {
+ return field_4_ole_classname;
+ }
+
+ public byte[] getObjectData() {
+ return field_6_unknown;
+ }
+
+
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append("[ftPictFmla]\n");
+ sb.append(" .f2unknown = ").append(HexDump.intToHex(field_1_unknown_int)).append("\n");
+ if (field_2_refPtg == null) {
+ sb.append(" .f3unknown = ").append(HexDump.toHex(field_2_unknownFormulaData)).append("\n");
+ } else {
+ sb.append(" .formula = ").append(field_2_refPtg.toString()).append("\n");
+ }
+ if (field_4_ole_classname != null) {
+ sb.append(" .unicodeFlag = ").append(field_3_unicode_flag).append("\n");
+ sb.append(" .oleClassname = ").append(field_4_ole_classname).append("\n");
+ }
+ if (field_4_unknownByte != null) {
+ sb.append(" .f4unknown = ").append(HexDump.byteToHex(field_4_unknownByte.intValue())).append("\n");
+ }
+ if (field_5_stream_id != null) {
+ sb.append(" .streamId = ").append(HexDump.intToHex(field_5_stream_id.intValue())).append("\n");
+ }
+ if (field_6_unknown.length > 0) {
+ sb.append(" .f7unknown = ").append(HexDump.toHex(field_6_unknown)).append("\n");
+ }
+ sb.append("[/ftPictFmla]");
+ return sb.toString();
+ }
}
diff --git a/src/java/org/apache/poi/hssf/record/RecordInputStream.java b/src/java/org/apache/poi/hssf/record/RecordInputStream.java
index fe6a4b2ea3..c23868c711 100755
--- a/src/java/org/apache/poi/hssf/record/RecordInputStream.java
+++ b/src/java/org/apache/poi/hssf/record/RecordInputStream.java
@@ -33,7 +33,7 @@ public class RecordInputStream extends InputStream {
/** Maximum size of a single record (minus the 4 byte header) without a continue*/
public final static short MAX_RECORD_DATA_SIZE = 8224;
private static final int INVALID_SID_VALUE = -1;
-
+
private InputStream in;
protected short currentSid;
protected short currentLength = -1;
@@ -42,34 +42,34 @@ public class RecordInputStream extends InputStream {
protected byte[] data = new byte[MAX_RECORD_DATA_SIZE];
protected short recordOffset;
protected long pos;
-
+
private boolean autoContinue = true;
- public RecordInputStream(InputStream in) throws RecordFormatException {
+ public RecordInputStream(InputStream in) throws RecordFormatException {
this.in = in;
try {
nextSid = LittleEndian.readShort(in);
- //Dont increment the pos just yet (technically we are at the start of
- //the record stream until nextRecord is called).
+ //Don't increment the pos just yet (technically we are at the start of
+ //the record stream until nextRecord is called).
} catch (IOException ex) {
throw new RecordFormatException("Error reading bytes", ex);
}
}
-
- /** This method will read a byte from the current record*/
- public int read() {
- checkRecordPosition();
- byte result = data[recordOffset];
- recordOffset += 1;
- pos += 1;
- return result;
- }
-
+ /** This method will read a byte from the current record*/
+ public int read() {
+ checkRecordPosition(LittleEndian.BYTE_SIZE);
+
+ byte result = data[recordOffset];
+ recordOffset += LittleEndian.BYTE_SIZE;
+ pos += LittleEndian.BYTE_SIZE;
+ return result;
+ }
+
public short getSid() {
return currentSid;
}
-
+
public short getLength() {
return currentLength;
}
@@ -85,12 +85,11 @@ public class RecordInputStream extends InputStream {
public boolean hasNextRecord() {
return nextSid != INVALID_SID_VALUE;
}
-
+
/** Moves to the next record in the stream.
- *
+ *
* Note: The auto continue flag is reset to true
*/
-
public void nextRecord() throws RecordFormatException {
if ((currentLength != -1) && (currentLength != recordOffset)) {
System.out.println("WARN. Unread "+remaining()+" bytes of record 0x"+Integer.toHexString(currentSid));
@@ -100,7 +99,7 @@ public class RecordInputStream extends InputStream {
autoContinue = true;
try {
recordOffset = 0;
- currentLength = LittleEndian.readShort(in);
+ currentLength = LittleEndian.readShort(in);
if (currentLength > MAX_RECORD_DATA_SIZE)
throw new RecordFormatException("The content of an excel record cannot exceed "+MAX_RECORD_DATA_SIZE+" bytes");
pos += LittleEndian.SHORT_SIZE;
@@ -113,138 +112,124 @@ public class RecordInputStream extends InputStream {
// ex45582-22397.xls has one extra byte after the last record
// Excel reads that file OK
}
- nextSid = INVALID_SID_VALUE;
+ nextSid = INVALID_SID_VALUE;
} else {
nextSid = LittleEndian.readShort(in);
if (nextSid == INVALID_SID_VALUE) {
throw new RecordFormatException("Found sid " + nextSid + " after record with sid 0x"
+ Integer.toHexString(currentSid).toUpperCase());
}
- }
+ }
} catch (IOException ex) {
throw new RecordFormatException("Error reading bytes", ex);
}
}
-
+
public void setAutoContinue(boolean enable) {
- this.autoContinue = enable;
+ this.autoContinue = enable;
}
-
+
public boolean getAutoContinue() {
return autoContinue;
}
-
- protected void checkRecordPosition() {
- if (remaining() <= 0) {
- if (isContinueNext() && autoContinue) {
- nextRecord();
- }
- else throw new ArrayIndexOutOfBoundsException();
- }
- }
-
- /**
- * Reads an 8 bit, signed value
- */
- public byte readByte() {
- checkRecordPosition();
-
- byte result = data[recordOffset];
- recordOffset += 1;
- pos += 1;
- return result;
- }
-
- /**
- * Reads a 16 bit, signed value
- */
- public short readShort() {
- checkRecordPosition();
-
- short result = LittleEndian.getShort(data, recordOffset);
- recordOffset += LittleEndian.SHORT_SIZE;
- pos += LittleEndian.SHORT_SIZE;
- return result;
- }
- public int readInt() {
- checkRecordPosition();
-
- int result = LittleEndian.getInt(data, recordOffset);
- recordOffset += LittleEndian.INT_SIZE;
- pos += LittleEndian.INT_SIZE;
- return result;
- }
+ private void checkRecordPosition(int requiredByteCount) {
- public long readLong() {
- checkRecordPosition();
-
- long result = LittleEndian.getLong(data, recordOffset);
- recordOffset += LittleEndian.LONG_SIZE;
- pos += LittleEndian.LONG_SIZE;
- return result;
- }
+ if (remaining() < requiredByteCount) {
+ if (isContinueNext() && autoContinue) {
+ nextRecord();
+ } else {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ }
+ }
+
+ /**
+ * Reads an 8 bit, signed value
+ */
+ public byte readByte() {
+ checkRecordPosition(LittleEndian.BYTE_SIZE);
+
+ byte result = data[recordOffset];
+ recordOffset += LittleEndian.BYTE_SIZE;
+ pos += LittleEndian.BYTE_SIZE;
+ return result;
+ }
+
+ /**
+ * Reads a 16 bit, signed value
+ */
+ public short readShort() {
+ checkRecordPosition(LittleEndian.SHORT_SIZE);
+
+ short result = LittleEndian.getShort(data, recordOffset);
+ recordOffset += LittleEndian.SHORT_SIZE;
+ pos += LittleEndian.SHORT_SIZE;
+ return result;
+ }
+
+ public int readInt() {
+ checkRecordPosition(LittleEndian.INT_SIZE);
+
+ int result = LittleEndian.getInt(data, recordOffset);
+ recordOffset += LittleEndian.INT_SIZE;
+ pos += LittleEndian.INT_SIZE;
+ return result;
+ }
+
+ public long readLong() {
+ checkRecordPosition(LittleEndian.LONG_SIZE);
+
+ long result = LittleEndian.getLong(data, recordOffset);
+ recordOffset += LittleEndian.LONG_SIZE;
+ pos += LittleEndian.LONG_SIZE;
+ return result;
+ }
+
+ /**
+ * Reads an 8 bit, unsigned value
+ */
+ public short readUByte() {
+ return (short) (readByte() & 0x00FF);
+ }
+
+ /**
+ * Reads a 16 bit, unsigned value.
+ * @return
+ */
+ public int readUShort() {
+ checkRecordPosition(LittleEndian.SHORT_SIZE);
+
+ int result = LittleEndian.getUShort(data, recordOffset);
+ recordOffset += LittleEndian.SHORT_SIZE;
+ pos += LittleEndian.SHORT_SIZE;
+ return result;
+ }
+
+ public double readDouble() {
+ checkRecordPosition(LittleEndian.DOUBLE_SIZE);
+ long valueLongBits = LittleEndian.getLong(data, recordOffset);
+ double result = Double.longBitsToDouble(valueLongBits);
+ if (Double.isNaN(result)) {
+ throw new RuntimeException("Did not expect to read NaN"); // (Because Excel typically doesn't write NaN
+ }
+ recordOffset += LittleEndian.DOUBLE_SIZE;
+ pos += LittleEndian.DOUBLE_SIZE;
+ return result;
+ }
/**
- * Reads an 8 bit, unsigned value
- */
- public short readUByte() {
- short s = readByte();
- if(s < 0) {
- s += 256;
- }
- return s;
- }
-
- /**
- * Reads a 16 bit,un- signed value.
- * @return
- */
- public int readUShort() {
- checkRecordPosition();
-
- int result = LittleEndian.getUShort(data, recordOffset);
- recordOffset += LittleEndian.SHORT_SIZE;
- pos += LittleEndian.SHORT_SIZE;
- return result;
- }
-
- public double readDouble() {
- checkRecordPosition();
- long valueLongBits = LittleEndian.getLong(data, recordOffset);
- double result = Double.longBitsToDouble(valueLongBits);
- if (Double.isNaN(result)) {
- throw new RuntimeException("Did not expect to read NaN");
- }
- recordOffset += LittleEndian.DOUBLE_SIZE;
- pos += LittleEndian.DOUBLE_SIZE;
- return result;
- }
-
-
- public short[] readShortArray() {
- checkRecordPosition();
-
- short[] arr = LittleEndian.getShortArray(data, recordOffset);
- final int size = (2 * (arr.length +1));
- recordOffset += size;
- pos += size;
-
- return arr;
- }
-
- /**
- * given a byte array of 16-bit unicode characters, compress to 8-bit and
- * return a string
- *
- * { 0x16, 0x00 } -0x16
- *
+ * given a byte array of 16-bit unicode characters, compress to 8-bit and
+ * return a string
+ *
+ * { 0x16, 0x00 } -0x16
+ *
* @param length the length of the final string
* @return the converted string
* @exception IllegalArgumentException if len is too large (i.e.,
- * there is not enough data in string to create a String of that
- * length)
- */
+ * there is not enough data in string to create a String of that
+ * length)
+ */
public String readUnicodeLEString(int length) {
if ((length < 0) || (((remaining() / 2) < length) && !isContinueNext())) {
throw new IllegalArgumentException("Illegal length - asked for " + length + " but only " + (remaining()/2) + " left!");
@@ -258,11 +243,11 @@ public class RecordInputStream extends InputStream {
if(compressByte != 1) throw new IllegalArgumentException("compressByte in continue records must be 1 while reading unicode LE string");
}
char ch = (char)readShort();
- buf.append(ch);
+ buf.append(ch);
}
return buf.toString();
}
-
+
public String readCompressedUnicode(int length) {
if ((length < 0) || ((remaining() < length) && !isContinueNext())) {
throw new IllegalArgumentException("Illegal length " + length);
@@ -277,23 +262,23 @@ public class RecordInputStream extends InputStream {
}
byte b = readByte();
char ch = (char)(0x00FF & b); // avoid sex
- buf.append(ch);
+ buf.append(ch);
}
- return buf.toString();
+ return buf.toString();
}
-
+
/** Returns an excel style unicode string from the bytes reminaing in the record.
* Note: Unicode strings differ from normal strings due to the addition of
* formatting information.
- *
+ *
* @return The unicode string representation of the remaining bytes.
*/
public UnicodeString readUnicodeString() {
return new UnicodeString(this);
}
-
+
/** Returns the remaining bytes for the current record.
- *
+ *
* @return The remaining bytes of the current record.
*/
public byte[] readRemainder() {
@@ -304,39 +289,39 @@ public class RecordInputStream extends InputStream {
pos += size;
return result;
}
-
+
/** Reads all byte data for the current record, including any
* that overlaps into any following continue records.
- *
+ *
* @deprecated Best to write a input stream that wraps this one where there is
* special sub record that may overlap continue records.
- */
+ */
public byte[] readAllContinuedRemainder() {
//Using a ByteArrayOutputStream is just an easy way to get a
//growable array of the data.
ByteArrayOutputStream out = new ByteArrayOutputStream(2*MAX_RECORD_DATA_SIZE);
while (isContinueNext()) {
- byte[] b = readRemainder();
+ byte[] b = readRemainder();
out.write(b, 0, b.length);
nextRecord();
}
- byte[] b = readRemainder();
- out.write(b, 0, b.length);
-
+ byte[] b = readRemainder();
+ out.write(b, 0, b.length);
+
return out.toByteArray();
}
/** The remaining number of bytes in the current record.
- *
+ *
* @return The number of bytes remaining in the current record
*/
public int remaining() {
return (currentLength - recordOffset);
}
- /** Returns true iif a Continue record is next in the excel stream
- *
+ /** Returns true iif a Continue record is next in the excel stream
+ *
* @return True when a ContinueRecord is next.
*/
public boolean isContinueNext() {
diff --git a/src/java/org/apache/poi/hssf/record/SeriesListRecord.java b/src/java/org/apache/poi/hssf/record/SeriesListRecord.java
index 2894f15877..4753eae05f 100644
--- a/src/java/org/apache/poi/hssf/record/SeriesListRecord.java
+++ b/src/java/org/apache/poi/hssf/record/SeriesListRecord.java
@@ -1,4 +1,3 @@
-
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
@@ -15,66 +14,66 @@
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
-
package org.apache.poi.hssf.record;
-
-
-import org.apache.poi.util.*;
+import org.apache.poi.util.LittleEndian;
/**
- * The series list record defines the series displayed as an overlay to the main chart record.
- * NOTE: This source is automatically generated please do not modify this file. Either subclass or
- * remove the record in src/records/definitions.
-
+ *
+ * The series list record defines the series displayed as an overlay to the main chart record.
+ * TODO - does this record (0x1016) really exist. It doesn't seem to be referenced in either the OOO or MS doc
+ *
* @author Glen Stampoultzis (glens at apache.org)
*/
-public class SeriesListRecord
- extends Record
-{
- public final static short sid = 0x1016;
+public final class SeriesListRecord extends Record {
+ public final static short sid = 0x1016;
private short[] field_1_seriesNumbers;
-
- public SeriesListRecord()
- {
-
+ public SeriesListRecord(short[] seriesNumbers) {
+ field_1_seriesNumbers = seriesNumbers;
}
- public SeriesListRecord(RecordInputStream in)
- {
- field_1_seriesNumbers = in.readShortArray();
+ public SeriesListRecord(RecordInputStream in) {
+ int nItems = in.readUShort();
+ short[] ss = new short[nItems];
+ for (int i = 0; i < nItems; i++) {
+ ss[i] = in.readShort();
+
+ }
+ field_1_seriesNumbers = ss;
}
- public String toString()
- {
+ public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append("[SERIESLIST]\n");
- buffer.append(" .seriesNumbers = ")
- .append(" (").append( getSeriesNumbers() ).append(" )");
- buffer.append(System.getProperty("line.separator"));
+ buffer.append(" .seriesNumbers= ").append(" (").append( getSeriesNumbers() ).append(" )");
+ buffer.append("\n");
buffer.append("[/SERIESLIST]\n");
return buffer.toString();
}
- public int serialize(int offset, byte[] data)
- {
- int pos = 0;
+ public int serialize(int offset, byte[] data) {
- LittleEndian.putShort(data, 0 + offset, sid);
- LittleEndian.putShort(data, 2 + offset, (short)(getRecordSize() - 4));
+ int nItems = field_1_seriesNumbers.length;
+ int dataSize = 2 + 2 * nItems;
+
+ LittleEndian.putUShort(data, 0 + offset, sid);
+ LittleEndian.putUShort(data, 2 + offset, dataSize);
- LittleEndian.putShortArray(data, 4 + offset + pos, field_1_seriesNumbers);
+ LittleEndian.putUShort(data, 4 + offset, nItems);
+
+ int pos = offset + 6;
+ for (int i = 0; i < nItems; i++) {
+ LittleEndian.putUShort(data, pos, field_1_seriesNumbers[i]);
+ pos += 2;
+ }
- return getRecordSize();
+ return 4 + dataSize;
}
- /**
- * Size of record (exluding 4 byte header)
- */
public int getRecordSize()
{
return 4 + field_1_seriesNumbers.length * 2 + 2;
@@ -86,34 +85,23 @@ public class SeriesListRecord
}
public Object clone() {
- SeriesListRecord rec = new SeriesListRecord();
-
- rec.field_1_seriesNumbers = field_1_seriesNumbers;
- return rec;
+ return new SeriesListRecord((short[]) field_1_seriesNumbers.clone());
}
-
-
-
/**
* Get the series numbers field for the SeriesList record.
*/
- public short[] getSeriesNumbers()
- {
+ public short[] getSeriesNumbers() {
return field_1_seriesNumbers;
}
/**
* Set the series numbers field for the SeriesList record.
*/
- public void setSeriesNumbers(short[] field_1_seriesNumbers)
- {
+ public void setSeriesNumbers(short[] field_1_seriesNumbers) {
this.field_1_seriesNumbers = field_1_seriesNumbers;
}
-
-
-} // END OF CLASS
-
+}
diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFObjectData.java b/src/java/org/apache/poi/hssf/usermodel/HSSFObjectData.java
index 697c33b9e2..1db5883303 100644
--- a/src/java/org/apache/poi/hssf/usermodel/HSSFObjectData.java
+++ b/src/java/org/apache/poi/hssf/usermodel/HSSFObjectData.java
@@ -32,7 +32,7 @@ import org.apache.poi.util.HexDump;
*
* @author Daniel Noll
*/
-public class HSSFObjectData
+public final class HSSFObjectData
{
/**
* Underlying object record ultimately containing a reference to the object.
@@ -60,8 +60,7 @@ public class HSSFObjectData
* Returns the OLE2 Class Name of the object
*/
public String getOLE2ClassName() {
- EmbeddedObjectRefSubRecord subRecord = findObjectRecord();
- return subRecord.field_5_ole_classname;
+ return findObjectRecord().getOLEClassName();
}
/**
@@ -72,9 +71,9 @@ public class HSSFObjectData
* @throws IOException if there was an error reading the data.
*/
public DirectoryEntry getDirectory() throws IOException {
- EmbeddedObjectRefSubRecord subRecord = findObjectRecord();
+ EmbeddedObjectRefSubRecord subRecord = findObjectRecord();
- int streamId = ((EmbeddedObjectRefSubRecord) subRecord).getStreamId();
+ int streamId = subRecord.getStreamId().intValue();
String streamName = "MBD" + HexDump.toHex(streamId);
Entry entry = poifs.getRoot().getEntry(streamName);
@@ -91,8 +90,7 @@ public class HSSFObjectData
* Entry
*/
public byte[] getObjectData() {
- EmbeddedObjectRefSubRecord subRecord = findObjectRecord();
- return subRecord.remainingBytes;
+ return findObjectRecord().getObjectData();
}
/**
@@ -101,10 +99,11 @@ public class HSSFObjectData
* (Not all do, those that don't have a data portion)
*/
public boolean hasDirectoryEntry() {
- EmbeddedObjectRefSubRecord subRecord = findObjectRecord();
-
- // Field 6 tells you
- return (subRecord.field_6_stream_id != 0);
+ EmbeddedObjectRefSubRecord subRecord = findObjectRecord();
+
+ // 'stream id' field tells you
+ Integer streamId = subRecord.getStreamId();
+ return streamId != null && streamId.intValue() != 0;
}
/**
@@ -117,7 +116,7 @@ public class HSSFObjectData
while (subRecordIter.hasNext()) {
Object subRecord = subRecordIter.next();
if (subRecord instanceof EmbeddedObjectRefSubRecord) {
- return (EmbeddedObjectRefSubRecord)subRecord;
+ return (EmbeddedObjectRefSubRecord)subRecord;
}
}
diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java b/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java
index aacf663863..24ef987eee 100644
--- a/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java
+++ b/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java
@@ -724,8 +724,8 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm
HSSFName newName = new HSSFName(this, newNameRecord);
names.add(newName);
+ workbook.cloneDrawings(clonedSheet.getSheet());
}
- workbook.cloneDrawings(clonedSheet.getSheet());
// TODO - maybe same logic required for other/all built-in name records
return clonedSheet;
diff --git a/src/java/org/apache/poi/ss/formula/CellCacheEntry.java b/src/java/org/apache/poi/ss/formula/CellCacheEntry.java
index 861874c25b..bd7500023c 100644
--- a/src/java/org/apache/poi/ss/formula/CellCacheEntry.java
+++ b/src/java/org/apache/poi/ss/formula/CellCacheEntry.java
@@ -20,6 +20,7 @@ package org.apache.poi.ss.formula;
import java.util.HashSet;
import java.util.Set;
+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.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.NumberEval;
@@ -60,6 +61,9 @@ final class CellCacheEntry {
// value type is changing
return false;
}
+ if (a == BlankEval.INSTANCE) {
+ return b == a;
+ }
if (cls == NumberEval.class) {
return ((NumberEval)a).getNumberValue() == ((NumberEval)b).getNumberValue();
}
diff --git a/src/java/org/apache/poi/ss/formula/CellLocation.java b/src/java/org/apache/poi/ss/formula/CellLocation.java
index 6857c4bc1b..bb32e3dfa3 100644
--- a/src/java/org/apache/poi/ss/formula/CellLocation.java
+++ b/src/java/org/apache/poi/ss/formula/CellLocation.java
@@ -25,24 +25,24 @@ import org.apache.poi.hssf.util.CellReference;
final class CellLocation {
public static final CellLocation[] EMPTY_ARRAY = { };
- private final EvaluationWorkbook _book;
+ private final int _bookIx;
private final int _sheetIndex;
private final int _rowIndex;
private final int _columnIndex;
private final int _hashCode;
- public CellLocation(EvaluationWorkbook book, int sheetIndex, int rowIndex, int columnIndex) {
+ public CellLocation(int bookIx, int sheetIndex, int rowIndex, int columnIndex) {
if (sheetIndex < 0) {
throw new IllegalArgumentException("sheetIndex must not be negative");
}
- _book = book;
+ _bookIx = bookIx;
_sheetIndex = sheetIndex;
_rowIndex = rowIndex;
_columnIndex = columnIndex;
- _hashCode = System.identityHashCode(book) + sheetIndex + 17 * (rowIndex + 17 * columnIndex);
+ _hashCode = _bookIx + 17 * (sheetIndex + 17 * (rowIndex + 17 * columnIndex));
}
- public Object getBook() {
- return _book;
+ public int getBookIndex() {
+ return _bookIx;
}
public int getSheetIndex() {
return _sheetIndex;
@@ -65,7 +65,7 @@ final class CellLocation {
if (getSheetIndex() != other.getSheetIndex()) {
return false;
}
- if (getBook() != other.getBook()) {
+ if (getBookIndex() != other.getBookIndex()) {
return false;
}
return true;
diff --git a/src/java/org/apache/poi/ss/formula/CollaboratingWorkbooksEnvironment.java b/src/java/org/apache/poi/ss/formula/CollaboratingWorkbooksEnvironment.java
index c62d2f182d..7939596d3b 100644
--- a/src/java/org/apache/poi/ss/formula/CollaboratingWorkbooksEnvironment.java
+++ b/src/java/org/apache/poi/ss/formula/CollaboratingWorkbooksEnvironment.java
@@ -97,7 +97,7 @@ public final class CollaboratingWorkbooksEnvironment {
EvaluationCache cache = new EvaluationCache(evalListener);
for(int i=0; i