<action dev="POI-DEVELOPERS" type="add">Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx</action>
</release>
<release version="3.2-alpha1" date="2008-??-??">
+ <action dev="POI-DEVELOPERS" type="fix">45912 - fixed ArrayIndexOutOfBoundsException in EmbeddedObjectRefSubRecord</action>
+ <action dev="POI-DEVELOPERS" type="fix">45889 - fixed ArrayIndexOutOfBoundsException when constructing HSLF Table with a single row </action>
+ <action dev="POI-DEVELOPERS" type="add">Initial support for creating hyperlinks in HSLF</action>
<action dev="POI-DEVELOPERS" type="fix">45876 - fixed BoundSheetRecord to allow sheet names longer than 31 chars</action>
<action dev="POI-DEVELOPERS" type="add">45890 - fixed HSSFSheet.shiftRows to also update conditional formats</action>
<action dev="POI-DEVELOPERS" type="add">45865 modified Formula Parser/Evaluator to handle cross-worksheet formulas</action>
<action dev="POI-DEVELOPERS" type="add">Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx</action>
</release>
<release version="3.2-alpha1" date="2008-??-??">
+ <action dev="POI-DEVELOPERS" type="fix">45912 - fixed ArrayIndexOutOfBoundsException in EmbeddedObjectRefSubRecord</action>
+ <action dev="POI-DEVELOPERS" type="fix">45889 - fixed ArrayIndexOutOfBoundsException when constructing HSLF Table with a single row </action>
+ <action dev="POI-DEVELOPERS" type="add">Initial support for creating hyperlinks in HSLF</action>
<action dev="POI-DEVELOPERS" type="fix">45876 - fixed BoundSheetRecord to allow sheet names longer than 31 chars</action>
<action dev="POI-DEVELOPERS" type="add">45890 - fixed HSSFSheet.shiftRows to also update conditional formats</action>
<action dev="POI-DEVELOPERS" type="add">45865 modified Formula Parser/Evaluator to handle cross-worksheet formulas</action>
-
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
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)<br/>
* 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 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 int field_5_ole_classname_padding; // developer laziness...
- public byte[] remainingBytes;
-
- 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;
- }
-
- 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;
-
- 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 );
- }
-
- // 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
- }
-
- // Fetch the stream ID
- field_6_stream_id = in.readInt();
-
- // Store what's left
- remainingBytes = in.readRemainder();
- }
-
- public int serialize(int offset, byte[] data)
- {
- int pos = offset;
-
- LittleEndian.putShort(data, pos, sid); pos += 2;
- LittleEndian.putShort(data, pos, (short)(getRecordSize() - 4)); pos += 2;
-
- 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++;
-
- 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();
- }
-
- // 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;
-
- System.arraycopy(remainingBytes, 0, data, pos, remainingBytes.length);
-
- return getRecordSize();
- }
-
- /**
- * 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;
- }
-
- /**
- * 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 "MBD<var>xxxxxxxx</var>" where <var>xxxxxxxx</var> 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;
- }
-
- 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();
- }
+public final class EmbeddedObjectRefSubRecord extends SubRecord {
+ public static final short sid = 0x0009;
+
+ private static final byte[] EMPTY_BYTE_ARRAY = { };
+
+ 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;
+
+
+ // 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 short getSid() {
+ return sid;
+ }
+
+ 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 )
+
+ int streamIdOffset = in.readShort(); // OOO calls this 'nFmlaLen'
+
+ 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;
+ }
+
+ 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;
+
+ if (nUnexpectedPadding > 0) {
+ System.err.println("Discarding " + nUnexpectedPadding + " unexpected padding bytes ");
+ readRawData(in, nUnexpectedPadding);
+ }
+
+ // Fetch the stream ID
+ if (dataLenAfterFormula >= 4) {
+ field_5_stream_id = new Integer(in.readInt());
+ } else {
+ field_5_stream_id = null;
+ }
+
+ field_6_unknown = in.readRemainder();
+ }
+
+ 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;
+ }
+
+ 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) {
+
+ 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);
+ }
+
+ public int serialize(int base, byte[] data) {
+
+ int formulaSize = field_2_refPtg == null ? field_2_unknownFormulaData.length : field_2_refPtg.getSize();
+ int idOffset = getStreamIDOffset(formulaSize);
+ int dataSize = getDataSize(idOffset);
+
+
+ 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 "MBD<var>xxxxxxxx</var>" where <var>xxxxxxxx</var> is
+ * this ID converted into hex (in big endian order, funnily enough.)
+ *
+ * @return the data stream ID. Possibly <code>null</code>
+ */
+ 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();
+ }
}
/** 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;
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;
}
public boolean hasNextRecord() {
return nextSid != INVALID_SID_VALUE;
}
-
+
/** Moves to the next record in the stream.
- *
+ *
* <i>Note: The auto continue flag is reset to true</i>
*/
-
public void nextRecord() throws RecordFormatException {
if ((currentLength != -1) && (currentLength != recordOffset)) {
System.out.println("WARN. Unread "+remaining()+" bytes of record 0x"+Integer.toHexString(currentSid));
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;
// 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, unsigned value
- */
- public short readUByte() {
- short s = readByte();
- if(s < 0) {
- s += 256;
- }
- return s;
- }
+ /**
+ * Reads an 8 bit, signed value
+ */
+ public byte readByte() {
+ checkRecordPosition(LittleEndian.BYTE_SIZE);
- /**
- * 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;
- }
+ byte result = data[recordOffset];
+ recordOffset += LittleEndian.BYTE_SIZE;
+ pos += LittleEndian.BYTE_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;
- }
+ /**
+ * Reads a 16 bit, signed value
+ */
+ public short readShort() {
+ checkRecordPosition(LittleEndian.SHORT_SIZE);
-
- 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
- *
+ 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;
+ }
+
+ /**
+ * 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!");
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);
}
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.
* <i>Note:</i> Unicode strings differ from <b>normal</b> 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() {
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 <i>current</i> 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() {
-
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
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.<br/>
+ * 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;
}
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
-
+}
*
* @author Daniel Noll
*/
-public class HSSFObjectData
+public final class HSSFObjectData
{
/**
* Underlying object record ultimately containing a reference to the object.
* Returns the OLE2 Class Name of the object
*/
public String getOLE2ClassName() {
- EmbeddedObjectRefSubRecord subRecord = findObjectRecord();
- return subRecord.field_5_ole_classname;
+ return findObjectRecord().getOLEClassName();
}
/**
* @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);
* Entry
*/
public byte[] getObjectData() {
- EmbeddedObjectRefSubRecord subRecord = findObjectRecord();
- return subRecord.remainingBytes;
+ return findObjectRecord().getObjectData();
}
/**
* (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;
}
/**
while (subRecordIter.hasNext()) {
Object subRecord = subRecordIter.next();
if (subRecord instanceof EmbeddedObjectRefSubRecord) {
- return (EmbeddedObjectRefSubRecord)subRecord;
+ return (EmbeddedObjectRefSubRecord)subRecord;
}
}
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;
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;
// value type is changing
return false;
}
+ if (a == BlankEval.INSTANCE) {
+ return b == a;
+ }
if (cls == NumberEval.class) {
return ((NumberEval)a).getNumberValue() == ((NumberEval)b).getNumberValue();
}
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;
if (getSheetIndex() != other.getSheetIndex()) {
return false;
}
- if (getBook() != other.getBook()) {
+ if (getBookIndex() != other.getBookIndex()) {
return false;
}
return true;
EvaluationCache cache = new EvaluationCache(evalListener);
for(int i=0; i<nItems; i++) {
- evaluators[i].attachToEnvironment(env, cache);
+ evaluators[i].attachToEnvironment(env, cache, i);
}
}
CellLocation clB = (CellLocation) b;
int cmp;
- cmp = System.identityHashCode(clA.getBook()) - System.identityHashCode(clB.getBook());
+ cmp = clA.getBookIndex() - clB.getBookIndex();
if (cmp != 0) {
return cmp;
}
private final EvaluationWorkbook _workbook;
private EvaluationCache _cache;
+ private int _workbookIx;
private final IEvaluationListener _evaluationListener;
private final Map _sheetIndexesBySheet;
_cache = new EvaluationCache(evaluationListener);
_sheetIndexesBySheet = new IdentityHashMap();
_collaboratingWorkbookEnvironment = CollaboratingWorkbooksEnvironment.EMPTY;
+ _workbookIx = 0;
}
/**
System.out.println(s);
}
}
- /* package */ void attachToEnvironment(CollaboratingWorkbooksEnvironment collaboratingWorkbooksEnvironment, EvaluationCache cache) {
+ /* package */ void attachToEnvironment(CollaboratingWorkbooksEnvironment collaboratingWorkbooksEnvironment, EvaluationCache cache, int workbookIx) {
_collaboratingWorkbookEnvironment = collaboratingWorkbooksEnvironment;
_cache = cache;
+ _workbookIx = workbookIx;
}
/* package */ CollaboratingWorkbooksEnvironment getEnvironment() {
return _collaboratingWorkbookEnvironment;
/* package */ void detachFromEnvironment() {
_collaboratingWorkbookEnvironment = CollaboratingWorkbooksEnvironment.EMPTY;
_cache = new EvaluationCache(_evaluationListener);
+ _workbookIx = 0;
}
/* package */ IEvaluationListener getEvaluationListener() {
return _evaluationListener;
throw new IllegalArgumentException("value must not be null");
}
int sheetIndex = getSheetIndex(sheet);
- _cache.setValue(new CellLocation(_workbook, sheetIndex, rowIndex, columnIndex), true, CellLocation.EMPTY_ARRAY, value);
+ _cache.setValue(getCellLoc(sheetIndex, rowIndex, columnIndex), true, CellLocation.EMPTY_ARRAY, value);
}
/**
*/
public void notifySetFormula(Sheet sheet, int rowIndex, int columnIndex) {
int sheetIndex = getSheetIndex(sheet);
- _cache.setValue(new CellLocation(_workbook, sheetIndex, rowIndex, columnIndex), false, CellLocation.EMPTY_ARRAY, null);
+ _cache.setValue(getCellLoc(sheetIndex, rowIndex, columnIndex), false, CellLocation.EMPTY_ARRAY, null);
}
private int getSheetIndex(Sheet sheet) {
public ValueEval evaluate(Cell srcCell) {
int sheetIndex = getSheetIndex(srcCell.getSheet());
- CellLocation cellLoc = new CellLocation(_workbook, sheetIndex, srcCell.getRowIndex(), srcCell.getCellNum());
+ CellLocation cellLoc = getCellLoc(sheetIndex, srcCell.getRowIndex(), srcCell.getCellNum());
return internalEvaluate(srcCell, cellLoc, new EvaluationTracker(_cache));
}
} else {
cell = row.getCell(columnIndex);
}
- CellLocation cellLoc = new CellLocation(_workbook, sheetIndex, rowIndex, columnIndex);
+ CellLocation cellLoc = getCellLoc(sheetIndex, rowIndex, columnIndex);
tracker.acceptDependency(cellLoc);
return internalEvaluate(cell, cellLoc, tracker);
}
+ private CellLocation getCellLoc(int sheetIndex, int rowIndex, int columnIndex) {
+ return new CellLocation(_workbookIx, sheetIndex, rowIndex, columnIndex);
+ }
}
putNumber(data, offset, value, LittleEndianConsts.BYTE_SIZE);
}
- /**
- * put a array of shorts into a byte array
- *
- *@param data the byte array
- *@param offset a starting offset into the byte array
- *@param value the short array
- */
- public static void putShortArray(final byte[] data, final int offset, final short[] value) {
- putNumber(data, offset, value.length, SHORT_SIZE);
- for (int i = 0; i < value.length; i++) {
- putNumber(data, offset + 2 + (i * 2), value[i], SHORT_SIZE);
- }
- }
-
/**
* put an unsigned short value into a byte array
*
--- /dev/null
+/* ====================================================================\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to You under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+==================================================================== */\r
+package org.apache.poi.hslf.examples;\r
+\r
+import org.apache.poi.hslf.usermodel.SlideShow;\r
+import org.apache.poi.hslf.usermodel.RichTextRun;\r
+import org.apache.poi.hslf.model.*;\r
+\r
+import java.io.FileInputStream;\r
+import java.io.FileOutputStream;\r
+import java.awt.*;\r
+\r
+/**\r
+ * Demonstrates how to create hyperlinks in PowerPoint presentations\r
+ *\r
+ * @author Yegor Kozlov\r
+ */\r
+public class CreateHyperlink {\r
+\r
+ public static void main(String[] args) throws Exception {\r
+ SlideShow ppt = new SlideShow();\r
+\r
+ Slide slide = ppt.createSlide();\r
+\r
+ TextBox shape = new TextBox();\r
+ shape.setText("Apache POI");\r
+ Rectangle anchor = new Rectangle(100, 100, 200, 50);\r
+ shape.setAnchor(anchor);\r
+\r
+ String text = shape.getText();\r
+ Hyperlink link = new Hyperlink();\r
+ link.setAddress("http://www.apache.org");\r
+ link.setTitle(shape.getText());\r
+ int linkId = ppt.addHyperlink(link);\r
+\r
+ shape.setHyperlink(linkId, 0, text.length());\r
+\r
+ slide.addShape(shape);\r
+\r
+ FileOutputStream out = new FileOutputStream("hyperlink.ppt");\r
+ ppt.write(out);\r
+ out.close();\r
+\r
+ }\r
+}\r
public Table(int numrows, int numcols) {\r
super();\r
\r
+ if(numrows < 1) throw new IllegalArgumentException("The number of rows must be greater than 1");\r
+ if(numcols < 1) throw new IllegalArgumentException("The number of columns must be greater than 1");\r
+\r
int x=0, y=0, tblWidth=0, tblHeight=0;\r
cells = new TableCell[numrows][numcols];\r
for (int i = 0; i < cells.length; i++) {\r
Rectangle anchor = sh[i].getAnchor();\r
if(anchor.y != y0){\r
y0 = anchor.y;\r
- if(row != null) maxrowlen = Math.max(maxrowlen, row.size());\r
row = new ArrayList();\r
lst.add(row);\r
}\r
row.add(sh[i]);\r
+ maxrowlen = Math.max(maxrowlen, row.size());\r
}\r
}\r
cells = new TableCell[lst.size()][maxrowlen];\r
return (OEPlaceholderAtom)getClientDataRecord(RecordTypes.OEPlaceholderAtom.typeID);
}
+ /**
+ *
+ * Assigns a hyperlink to this text shape
+ *
+ * @param linkId id of the hyperlink, @see org.apache.poi.hslf.usermodel.SlideShow#addHyperlink(Hyperlink)
+ * @param beginIndex the beginning index, inclusive.
+ * @param endIndex the ending index, exclusive.
+ * @see org.apache.poi.hslf.usermodel.SlideShow#addHyperlink(Hyperlink)
+ */
+ public void setHyperlink(int linkId, int beginIndex, int endIndex){
+ //TODO validate beginIndex and endIndex and throw IllegalArgumentException
+
+ InteractiveInfo info = new InteractiveInfo();
+ InteractiveInfoAtom infoAtom = info.getInteractiveInfoAtom();
+ infoAtom.setAction(InteractiveInfoAtom.ACTION_HYPERLINK);
+ infoAtom.setHyperlinkType(InteractiveInfoAtom.LINK_Url);
+ infoAtom.setHyperlinkID(linkId);
+
+ _txtbox.appendChildRecord(info);
+
+ TxInteractiveInfoAtom txiatom = new TxInteractiveInfoAtom();
+ txiatom.setStartIndex(beginIndex);
+ txiatom.setEndIndex(endIndex);
+ _txtbox.appendChildRecord(txiatom);
+
+ }
+
}
/**\r
* Constructs a brand new link related atom record.\r
*/\r
- protected TxInteractiveInfoAtom() {\r
+ public TxInteractiveInfoAtom() {\r
_header = new byte[8];\r
_data = new byte[8];\r
\r
assertEquals(tbl.getNumberOfRows(), tbl3.getNumberOfRows());\r
}\r
\r
+ /**\r
+ * Error constructing Table when rownum=1\r
+ */\r
+ public void test45889(){\r
+ SlideShow ppt = new SlideShow();\r
+ Slide slide = ppt.createSlide();\r
+ Shape[] shapes;\r
+ Table tbl1 = new Table(1, 5);\r
+ assertEquals(5, tbl1.getNumberOfColumns());\r
+ assertEquals(1, tbl1.getNumberOfRows());\r
+ slide.addShape(tbl1);\r
+\r
+ shapes = slide.getShapes();\r
+ assertEquals(1, shapes.length);\r
+\r
+ Table tbl2 = (Table)shapes[0];\r
+ assertSame(tbl1.getSpContainer(), tbl2.getSpContainer());\r
+\r
+ assertEquals(tbl1.getNumberOfColumns(), tbl2.getNumberOfColumns());\r
+ assertEquals(tbl1.getNumberOfRows(), tbl2.getNumberOfRows());\r
+ }\r
+\r
+ public void testIllegalCOnstruction(){\r
+ try {\r
+ Table tbl = new Table(0, 5);\r
+ fail("Table(rownum, colnum) must throw IllegalArgumentException if any of tghe arguments is less than 1");\r
+ } catch (IllegalArgumentException e){\r
+\r
+ }\r
+ try {\r
+ Table tbl = new Table(5, 0);\r
+ fail("Table(rownum, colnum) must throw IllegalArgumentException if any of tghe arguments is less than 1");\r
+ } catch (IllegalArgumentException e){\r
+\r
+ }\r
+ }\r
}\r
==================================================================== */\r
package org.apache.poi.hssf.record;\r
\r
-import junit.framework.TestCase;\r
-import org.apache.poi.util.HexRead;\r
-\r
-import java.io.IOException;\r
import java.io.ByteArrayInputStream;\r
import java.util.Arrays;\r
\r
+import junit.framework.TestCase;\r
+\r
+import org.apache.poi.util.HexRead;\r
+\r
/**\r
* Tests the serialization and deserialization of the TestEmbeddedObjectRefSubRecord\r
* class works correctly. Test data taken directly from a real\r
*\r
* @author Yegor Kozlov\r
*/\r
-public class TestEmbeddedObjectRefSubRecord extends TestCase {\r
-\r
- String data1 = "[20, 00, 05, 00, FC, 10, 76, 01, 02, 24, 14, DF, 00, 03, 10, 00, 00, 46, 6F, 72, 6D, 73, 2E, 43, 68, 65, 63, 6B, 42, 6F, 78, 2E, 31, 00, 00, 00, 00, 00, 70, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, ]";\r
-\r
- public void testStore() throws IOException {\r
-\r
- byte[] src = HexRead.readFromString(data1);\r
- src = TestcaseRecordInputStream.mergeDataAndSid(EmbeddedObjectRefSubRecord.sid, (short)src.length, src);\r
-\r
- RecordInputStream in = new RecordInputStream(new ByteArrayInputStream(src));\r
- in.nextRecord();\r
-\r
- EmbeddedObjectRefSubRecord record1 = new EmbeddedObjectRefSubRecord(in);\r
-\r
- byte[] ser = record1.serialize();\r
-\r
- RecordInputStream in2 = new RecordInputStream(new ByteArrayInputStream(ser));\r
- in2.nextRecord();\r
- EmbeddedObjectRefSubRecord record2 = new EmbeddedObjectRefSubRecord(in2);\r
-\r
- assertTrue(Arrays.equals(src, ser));\r
- assertEquals(record1.field_1_stream_id_offset, record2.field_1_stream_id_offset);\r
- assertTrue(Arrays.equals(record1.field_2_unknown, record2.field_2_unknown));\r
- assertEquals(record1.field_3_unicode_len, record2.field_3_unicode_len);\r
- assertEquals(record1.field_4_unicode_flag, record2.field_4_unicode_flag);\r
- assertEquals(record1.field_5_ole_classname, record2.field_5_ole_classname);\r
- assertEquals(record1.field_6_stream_id, record2.field_6_stream_id);\r
- assertTrue(Arrays.equals(record1.remainingBytes, record2.remainingBytes));\r
- }\r
-\r
- public void testCreate() throws IOException {\r
-\r
-\r
- EmbeddedObjectRefSubRecord record1 = new EmbeddedObjectRefSubRecord();\r
-\r
- byte[] ser = record1.serialize();\r
- RecordInputStream in2 = new RecordInputStream(new ByteArrayInputStream(ser));\r
- in2.nextRecord();\r
- EmbeddedObjectRefSubRecord record2 = new EmbeddedObjectRefSubRecord(in2);\r
-\r
- assertEquals(record1.field_1_stream_id_offset, record2.field_1_stream_id_offset);\r
- assertTrue(Arrays.equals(record1.field_2_unknown, record2.field_2_unknown));\r
- assertEquals(record1.field_3_unicode_len, record2.field_3_unicode_len);\r
- assertEquals(record1.field_4_unicode_flag, record2.field_4_unicode_flag);\r
- assertEquals(record1.field_5_ole_classname, record2.field_5_ole_classname);\r
- assertEquals(record1.field_6_stream_id, record2.field_6_stream_id);\r
- assertTrue(Arrays.equals(record1.remainingBytes, record2.remainingBytes));\r
+public final class TestEmbeddedObjectRefSubRecord extends TestCase {\r
\r
- }\r
+ String data1 = "[20, 00, 05, 00, FC, 10, 76, 01, 02, 24, 14, DF, 00, 03, 10, 00, 00, 46, 6F, 72, 6D, 73, 2E, 43, 68, 65, 63, 6B, 42, 6F, 78, 2E, 31, 00, 00, 00, 00, 00, 70, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, ]";\r
+\r
+ public void testStore() {\r
+\r
+ byte[] src = hr(data1);\r
+ src = TestcaseRecordInputStream.mergeDataAndSid(EmbeddedObjectRefSubRecord.sid, (short)src.length, src);\r
+\r
+ RecordInputStream in = new RecordInputStream(new ByteArrayInputStream(src));\r
+ in.nextRecord();\r
+\r
+ EmbeddedObjectRefSubRecord record1 = new EmbeddedObjectRefSubRecord(in);\r
+\r
+ byte[] ser = record1.serialize();\r
+\r
+ RecordInputStream in2 = new RecordInputStream(new ByteArrayInputStream(ser));\r
+ in2.nextRecord();\r
+ EmbeddedObjectRefSubRecord record2 = new EmbeddedObjectRefSubRecord(in2);\r
+\r
+ assertTrue(Arrays.equals(src, ser));\r
+ assertEquals(record1.getOLEClassName(), record2.getOLEClassName());\r
+\r
+ byte[] ser2 = record1.serialize();\r
+ assertTrue(Arrays.equals(ser, ser2));\r
+ }\r
+\r
+ public void testCreate() {\r
+\r
+ EmbeddedObjectRefSubRecord record1 = new EmbeddedObjectRefSubRecord();\r
+\r
+ byte[] ser = record1.serialize();\r
+ RecordInputStream in2 = new RecordInputStream(new ByteArrayInputStream(ser));\r
+ in2.nextRecord();\r
+ EmbeddedObjectRefSubRecord record2 = new EmbeddedObjectRefSubRecord(in2);\r
+\r
+ assertEquals(record1.getOLEClassName(), record2.getOLEClassName());\r
+ assertEquals(record1.getStreamId(), record2.getStreamId());\r
+\r
+ byte[] ser2 = record1.serialize();\r
+ assertTrue(Arrays.equals(ser, ser2));\r
+ }\r
+\r
+\r
+ /**\r
+ * taken from ftPictFmla sub-record in attachment 22645 (offset 0x40AB).\r
+ */\r
+ private static final byte[] data45912 = hr(\r
+ "09 00 14 00 " +\r
+ "12 00 0B 00 F8 02 88 04 3B 00 " +\r
+ "00 00 00 01 00 00 00 01 " +\r
+ "00 00");\r
+\r
+ public void testCameraTool_bug45912() {\r
+ byte[] data = data45912;\r
+ RecordInputStream in = new RecordInputStream(new ByteArrayInputStream(data));\r
+ in.nextRecord();\r
+\r
+ EmbeddedObjectRefSubRecord rec = new EmbeddedObjectRefSubRecord(in);\r
+ byte[] ser2 = rec.serialize();\r
+ assertTrue(Arrays.equals(data, ser2));\r
+\r
+\r
+ }\r
+\r
+ private static byte[] hr(String string) {\r
+ return HexRead.readFromString(string);\r
+ }\r
+\r
+ /**\r
+ * tests various examples of OLE controls\r
+ */\r
+ public void testVarious() {\r
+ String[] rawData = {\r
+ "12 00 0B 00 70 95 0B 05 3B 01 00 36 00 40 00 18 00 19 00 18",\r
+ "12 00 0B 00 B0 4D 3E 03 3B 00 00 00 00 01 00 00 80 01 C0 00",\r
+ "0C 00 05 00 60 AF 3B 03 24 FD FF FE C0 FE",\r
+ "24 00 05 00 40 42 3E 03 02 80 CD B4 04 03 15 00 00 46 6F 72 6D 73 2E 43 6F 6D 6D 61 6E 64 42 75 74 74 6F 6E 2E 31 00 00 00 00 54 00 00 00 00 00 00 00 00 00 00 00",\r
+ "22 00 05 00 10 4E 3E 03 02 00 4C CC 04 03 12 00 00 46 6F 72 6D 73 2E 53 70 69 6E 42 75 74 74 6F 6E 2E 31 00 54 00 00 00 20 00 00 00 00 00 00 00 00 00 00 00",\r
+ "20 00 05 00 E0 41 3E 03 02 00 FC 0B 05 03 10 00 00 46 6F 72 6D 73 2E 43 6F 6D 62 6F 42 6F 78 2E 31 00 74 00 00 00 4C 00 00 00 00 00 00 00 00 00 00 00",\r
+ "24 00 05 00 00 4C AF 03 02 80 E1 93 05 03 14 00 00 46 6F 72 6D 73 2E 4F 70 74 69 6F 6E 42 75 74 74 6F 6E 2E 31 00 C0 00 00 00 70 00 00 00 00 00 00 00 00 00 00 00",\r
+ "20 00 05 00 E0 A4 28 04 02 80 EA 93 05 03 10 00 00 46 6F 72 6D 73 2E 43 68 65 63 6B 42 6F 78 2E 31 00 30 01 00 00 6C 00 00 00 00 00 00 00 00 00 00 00",\r
+ "1C 00 05 00 30 40 3E 03 02 00 CC B4 04 03 0D 00 00 46 6F 72 6D 73 2E 4C 61 62 65 6C 2E 31 9C 01 00 00 54 00 00 00 00 00 00 00 00 00 00 00",\r
+ "1E 00 05 00 B0 A4 28 04 02 00 D0 0A 05 03 0F 00 00 46 6F 72 6D 73 2E 4C 69 73 74 42 6F 78 2E 31 F0 01 00 00 48 00 00 00 00 00 00 00 00 00 00 00",\r
+ "24 00 05 00 C0 AF 3B 03 02 80 D1 0A 05 03 14 00 00 46 6F 72 6D 73 2E 54 6F 67 67 6C 65 42 75 74 74 6F 6E 2E 31 00 38 02 00 00 6C 00 00 00 00 00 00 00 00 00 00 00",\r
+ "1E 00 05 00 90 AF 3B 03 02 80 D4 0A 05 03 0F 00 00 46 6F 72 6D 73 2E 54 65 78 74 42 6F 78 2E 31 A4 02 00 00 48 00 00 00 00 00 00 00 00 00 00 00",\r
+ "24 00 05 00 60 40 3E 03 02 00 D6 0A 05 03 14 00 00 46 6F 72 6D 73 2E 54 6F 67 67 6C 65 42 75 74 74 6F 6E 2E 31 00 EC 02 00 00 6C 00 00 00 00 00 00 00 00 00 00 00",\r
+ "20 00 05 00 20 4D 3E 03 02 00 D9 0A 05 03 11 00 00 46 6F 72 6D 73 2E 53 63 72 6F 6C 6C 42 61 72 2E 31 58 03 00 00 20 00 00 00 00 00 00 00 00 00 00 00",\r
+ "20 00 05 00 00 AF 28 04 02 80 31 AC 04 03 10 00 00 53 68 65 6C 6C 2E 45 78 70 6C 6F 72 65 72 2E 32 00 78 03 00 00 AC 00 00 00 00 00 00 00 00 00 00 00",\r
+ };\r
+\r
+ for (int i = 0; i < rawData.length; i++) {\r
+ confirmRead(hr(rawData[i]), i);\r
+ }\r
+ }\r
+\r
+ private static void confirmRead(byte[] data, int i) {\r
+ RecordInputStream in = new TestcaseRecordInputStream(EmbeddedObjectRefSubRecord.sid, (short)data.length, data);\r
+\r
+ EmbeddedObjectRefSubRecord rec = new EmbeddedObjectRefSubRecord(in);\r
+ byte[] ser2 = rec.serialize();\r
+ byte[] d2 = (byte[]) data.clone(); // remove sid+len for compare\r
+ System.arraycopy(ser2, 4, d2, 0, d2.length);\r
+ if (!Arrays.equals(data, d2)) {\r
+ fail("re-read NQR for case " + i);\r
+ }\r
+ }\r
}\r
* @author Glen Stampoultzis (glens at apache.org)
*/
public final class TestSeriesListRecord extends TestCase {
- byte[] data = new byte[] {
+ private static final byte[] data = {
(byte)0x02,(byte)0x00,(byte)0x01,(byte)0x20,(byte)0xff,(byte)0xf0
};
assertEquals( 4 + 6, record.getRecordSize() );
}
- public void testStore()
- {
- SeriesListRecord record = new SeriesListRecord();
- record.setSeriesNumbers( new short[] { (short)0x2001, (short)0xf0ff } );
+ public void testStore() {
+ SeriesListRecord record = new SeriesListRecord(new short[] { (short)0x2001, (short)0xf0ff } );
byte [] recordBytes = record.serialize();
assertEquals(recordBytes.length - 4, data.length);
public void test44840() {
HSSFWorkbook wb = openSample("WithCheckBoxes.xls");
- // Take a look at the embeded objects
+ // Take a look at the embedded objects
List objects = wb.getAllEmbeddedObjects();
assertEquals(1, objects.size());
EmbeddedObjectRefSubRecord rec = obj.findObjectRecord();
assertNotNull(rec);
- assertEquals(32, rec.field_1_stream_id_offset);
- assertEquals(0, rec.field_6_stream_id); // WRONG!
- assertEquals("Forms.CheckBox.1", rec.field_5_ole_classname);
- assertEquals(12, rec.remainingBytes.length);
+// assertEquals(32, rec.field_1_stream_id_offset);
+ assertEquals(0, rec.getStreamId().intValue()); // WRONG!
+ assertEquals("Forms.CheckBox.1", rec.getOLEClassName());
+ assertEquals(12, rec.getObjectData().length);
// Doesn't have a directory
assertFalse(obj.hasDirectoryEntry());
obj.getDirectory();
fail();
} catch(FileNotFoundException e) {
- // expectd during successful test
+ // expected during successful test
} catch (IOException e) {
throw new RuntimeException(e);
}
*/
public final class AllSSFormulaTests {
public static Test suite() {
- TestSuite result = new TestSuite(AllSSFormulaTests.class.getName());
- result.addTestSuite(TestEvaluationCache.class);
- result.addTestSuite(TestWorkbookEvaluator.class);
- return result;
- }
+ TestSuite result = new TestSuite(AllSSFormulaTests.class.getName());
+ result.addTestSuite(TestCellCacheEntry.class);
+ result.addTestSuite(TestEvaluationCache.class);
+ result.addTestSuite(TestWorkbookEvaluator.class);
+ return result;
+ }
}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula;
+
+import org.apache.poi.hssf.record.formula.eval.BlankEval;
+import org.apache.poi.hssf.record.formula.eval.NumberEval;
+import org.apache.poi.hssf.record.formula.eval.ValueEval;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.TestCase;
+
+/**
+ * Tests {@link CellCacheEntry}.
+ *
+ * @author Josh Micich
+ */
+public class TestCellCacheEntry extends TestCase {
+
+ public void testBasic() {
+ CellCacheEntry cce = new CellCacheEntry();
+ cce.updatePlainValue(new NumberEval(42.0));
+ ValueEval ve = cce.getValue();
+ assertEquals(42, ((NumberEval)ve).getNumberValue(), 0.0);
+
+ cce.setFormulaResult(new NumberEval(10.0), new CellLocation[] { });
+ }
+
+ public void testBlank() {
+ CellCacheEntry cce = new CellCacheEntry();
+ cce.updatePlainValue(BlankEval.INSTANCE);
+ try {
+ cce.updatePlainValue(BlankEval.INSTANCE);
+ } catch (IllegalStateException e) {
+ // bug was visible around svn r700356
+ throw new AssertionFailedError("cache entry does not handle blank values properly");
+ }
+ }
+}