<!-- Don't forget to update status.xml too! -->
<release version="3.5-beta4" date="2008-??-??">
+ <action dev="POI-DEVELOPERS" type="fix">46199 - More tweaks to EmbeddedObjectRefSubRecord</header>
<action dev="POI-DEVELOPERS" type="add">Changes to formula evaluation allowing for reduced memory usage</action>
<action dev="POI-DEVELOPERS" type="fix">45290 - Support odd files where the POIFS header block comes after the data blocks, and is on the data blocks list</header>
<action dev="POI-DEVELOPERS" type="fix">46184 - More odd escaped date formats</action>
<!-- Don't forget to update changes.xml too! -->
<changes>
<release version="3.5-beta4" date="2008-??-??">
+ <action dev="POI-DEVELOPERS" type="fix">46199 - More tweaks to EmbeddedObjectRefSubRecord</header>
<action dev="POI-DEVELOPERS" type="add">Changes to formula evaluation allowing for reduced memory usage</action>
<action dev="POI-DEVELOPERS" type="fix">45290 - Support odd files where the POIFS header block comes after the data blocks, and is on the data blocks list</header>
<action dev="POI-DEVELOPERS" type="fix">46184 - More odd escaped date formats</action>
private int field_1_unknown_int;
/** either an area or a cell ref */
private Ptg field_2_refPtg;
+ /** for when the 'formula' doesn't parse properly */
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.
+ /** note- this byte is not present in the encoding if the string length is zero */
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.
}
public EmbeddedObjectRefSubRecord(LittleEndianInput in, int size) {
- // TODO use 'size' param
+
// 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
int stringByteCount;
if (remaining >= dataLenAfterFormula + 3) {
int tag = in.readByte();
- remaining -= LittleEndian.BYTE_SIZE;
+ stringByteCount = LittleEndian.BYTE_SIZE;
if (tag != 0x03) {
throw new RecordFormatException("Expected byte 0x03 here");
}
int nChars = in.readUShort();
- remaining -= LittleEndian.SHORT_SIZE;
+ stringByteCount += LittleEndian.SHORT_SIZE;
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;
- remaining -= LittleEndian.BYTE_SIZE;
+ stringByteCount += LittleEndian.BYTE_SIZE;
if (field_3_unicode_flag) {
field_4_ole_classname = StringUtil.readUnicodeLE(in, nChars);
- stringByteCount = nChars * 2;
+ stringByteCount += nChars * 2;
} else {
field_4_ole_classname = StringUtil.readCompressedUnicode(in, nChars);
- stringByteCount = nChars;
+ stringByteCount += nChars;
}
} else {
field_4_ole_classname = "";
- stringByteCount = 0;
}
} else {
field_4_ole_classname = null;
} else {
field_5_stream_id = null;
}
-
- byte [] buf = new byte[remaining];
- in.readFully(buf);
- field_6_unknown = buf;
+ field_6_unknown = readRawData(in, remaining);
}
private static Ptg readRefPtg(byte[] formulaRawBytes) {
return EMPTY_BYTE_ARRAY;
}
byte[] result = new byte[size];
- for(int i=0; i< size; i++) {
- result[i] = in.readByte();
- }
+ in.readFully(result);
return result;
}
// don't write 0x03, stringLen, flag, text
stringLen = 0;
} else {
- result += 1 + 2 + 1; // 0x03, stringLen, flag
+ result += 1 + 2; // 0x03, stringLen
stringLen = field_4_ole_classname.length();
- if (field_3_unicode_flag) {
- result += stringLen * 2;
- } else {
- result += stringLen;
+ if (stringLen > 0) {
+ result += 1; // flag
+ if (field_3_unicode_flag) {
+ result += stringLen * 2;
+ } else {
+ result += stringLen;
+ }
}
}
// pad to next 2 byte boundary
stringLen = field_4_ole_classname.length();
out.writeShort(stringLen);
pos+=2;
- out.writeByte(field_3_unicode_flag ? 0x01 : 0x00);
- pos+=1;
-
- if (field_3_unicode_flag) {
- StringUtil.putUnicodeLE(field_4_ole_classname, out);
- pos += stringLen * 2;
- } else {
- StringUtil.putCompressedUnicode(field_4_ole_classname, out);
- pos += stringLen;
+ if (stringLen > 0) {
+ out.writeByte(field_3_unicode_flag ? 0x01 : 0x00);
+ pos+=1;
+
+ if (field_3_unicode_flag) {
+ StringUtil.putUnicodeLE(field_4_ole_classname, out);
+ pos += stringLen * 2;
+ } else {
+ StringUtil.putCompressedUnicode(field_4_ole_classname, out);
+ pos += stringLen;
+ }
}
}
*/\r
public final 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
+ private static final short EORSR_SID = EmbeddedObjectRefSubRecord.sid;\r
\r
public void testStore() {\r
+ String data1\r
+ = "20 00 05 00 FC 10 76 01 02 24 14 DF 00 03 10 00 "\r
+ + "00 46 6F 72 6D 73 2E 43 68 65 63 6B 42 6F 78 2E "\r
+ + "31 00 00 00 00 00 70 00 00 00 00 00 00 00 00 00 "\r
+ + "00 00";\r
\r
byte[] src = hr(data1);\r
-// src = TestcaseRecordInputStream.mergeDataAndSid(EmbeddedObjectRefSubRecord.sid, (short)src.length, src);\r
\r
- RecordInputStream in = TestcaseRecordInputStream.create(EmbeddedObjectRefSubRecord.sid, src);\r
+ RecordInputStream in = TestcaseRecordInputStream.create(EORSR_SID, src);\r
\r
EmbeddedObjectRefSubRecord record1 = new EmbeddedObjectRefSubRecord(in, src.length);\r
\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
- "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 = TestcaseRecordInputStream.create(EmbeddedObjectRefSubRecord.sid, data);\r
-\r
- EmbeddedObjectRefSubRecord rec = new EmbeddedObjectRefSubRecord(in, data.length);\r
+ /**\r
+ * taken from ftPictFmla sub-record in attachment 22645 (offset 0x40AB).\r
+ */\r
+ byte[] data45912 = hr(\r
+ "12 00 0B 00 F8 02 88 04 3B 00 " +\r
+ "00 00 00 01 00 00 00 01 " +\r
+ "00 00");\r
+ RecordInputStream in = TestcaseRecordInputStream.create(EORSR_SID, data45912);\r
+\r
+ EmbeddedObjectRefSubRecord rec = new EmbeddedObjectRefSubRecord(in, data45912.length);\r
byte[] ser2 = rec.serialize();\r
- confirmData(data, ser2);\r
+ confirmData(data45912, ser2);\r
}\r
\r
private static byte[] hr(String string) {\r
}\r
\r
private static void confirmRead(byte[] data, int i) {\r
- RecordInputStream in = TestcaseRecordInputStream.create(EmbeddedObjectRefSubRecord.sid, data);\r
+ RecordInputStream in = TestcaseRecordInputStream.create(EORSR_SID, data);\r
\r
EmbeddedObjectRefSubRecord rec = new EmbeddedObjectRefSubRecord(in, data.length);\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
+ TestcaseRecordInputStream.confirmRecordEncoding("Test record " + i, EORSR_SID, data, ser2);\r
+ }\r
+ \r
+ public void testVisioDrawing_bug46199() {\r
+ /**\r
+ * taken from ftPictFmla sub-record in attachment 22860 (stream offset 0x768F).<br/>\r
+ * Note that the since the string length is zero, there is no unicode flag byte\r
+ */\r
+ byte[] data46199 = hr(\r
+ "0E 00 "\r
+ + "05 00 "\r
+ + "28 25 A3 01 "\r
+ + "02 6C D1 34 02 "\r
+ + "03 00 00 "\r
+ + "0F CB E8 00");\r
+ RecordInputStream in = TestcaseRecordInputStream.create(EORSR_SID, data46199);\r
+\r
+ EmbeddedObjectRefSubRecord rec;\r
+ try {\r
+ rec = new EmbeddedObjectRefSubRecord(in, data46199.length);\r
+ } catch (RecordFormatException e) {\r
+ if (e.getMessage().equals("Not enough data (3) to read requested (4) bytes")) {\r
+ throw new AssertionFailedError("Identified bug 22860");\r
+ }\r
+ throw e;\r
}\r
+ byte[] ser2 = rec.serialize();\r
+ TestcaseRecordInputStream.confirmRecordEncoding(EORSR_SID, data46199, ser2);\r
}\r
}\r
import java.io.InputStream;
import junit.framework.Assert;
+import junit.framework.AssertionFailedError;
+import org.apache.poi.util.HexDump;
import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.LittleEndianByteArrayInputStream;
import org.apache.poi.util.LittleEndianInput;
/**
* A Record Input Stream derivative that makes access to byte arrays used in the
* test cases work a bit easier.
- * <p> Creates the sream and moves to the first record.
+ * <p> Creates the stream and moves to the first record.
*
* @author Jason Height (jheight at apache.org)
*/
public final class TestcaseRecordInputStream {
-
+
private TestcaseRecordInputStream() {
// no instances of this class
}
-
+
/**
- * Prepends a mock record identifier to the supplied data and opens a record input stream
+ * Prepends a mock record identifier to the supplied data and opens a record input stream
*/
public static LittleEndianInput createLittleEndian(byte[] data) {
return new LittleEndianByteArrayInputStream(data);
-
+
}
public static RecordInputStream create(int sid, byte[] data) {
return create(mergeDataAndSid(sid, data.length, data));
}
/**
- * First 4 bytes of <tt>data</tt> are assumed to be record identifier and length. The supplied
- * <tt>data</tt> can contain multiple records (sequentially encoded in the same way)
+ * First 4 bytes of <tt>data</tt> are assumed to be record identifier and length. The supplied
+ * <tt>data</tt> can contain multiple records (sequentially encoded in the same way)
*/
public static RecordInputStream create(byte[] data) {
InputStream is = new ByteArrayInputStream(data);
result.nextRecord();
return result;
}
-
- /**
- * Convenience constructor
- */
-// public TestcaseRecordInputStream(int sid, byte[] data)
-// {
-// super(new ByteArrayInputStream(mergeDataAndSid(sid, data.length, data)));
-// nextRecord();
-// }
-// public TestcaseRecordInputStream(short sid, short length, byte[] data)
-// {
-// super(new ByteArrayInputStream(mergeDataAndSid(sid, length, data)));
-// nextRecord();
-// }
- public static byte[] mergeDataAndSid(int sid, int length, byte[] data) {
- byte[] result = new byte[data.length + 4];
- LittleEndian.putUShort(result, 0, sid);
- LittleEndian.putUShort(result, 2, length);
- System.arraycopy(data, 0, result, 4, data.length);
- return result;
- }
- /**
- * Confirms data sections are equal
- * @param expectedData - just raw data (without sid or size short ints)
- * @param actualRecordBytes this includes 4 prefix bytes (sid & size)
- */
- public static void confirmRecordEncoding(int expectedSid, byte[] expectedData, byte[] actualRecordBytes) {
- int expectedDataSize = expectedData.length;
- Assert.assertEquals(actualRecordBytes.length - 4, expectedDataSize);
- Assert.assertEquals(expectedSid, LittleEndian.getShort(actualRecordBytes, 0));
- Assert.assertEquals(expectedDataSize, LittleEndian.getShort(actualRecordBytes, 2));
- for (int i = 0; i < expectedDataSize; i++)
- Assert.assertEquals("At offset " + i, expectedData[i], actualRecordBytes[i+4]);
-
- }
+ public static byte[] mergeDataAndSid(int sid, int length, byte[] data) {
+ byte[] result = new byte[data.length + 4];
+ LittleEndian.putUShort(result, 0, sid);
+ LittleEndian.putUShort(result, 2, length);
+ System.arraycopy(data, 0, result, 4, data.length);
+ return result;
+ }
+ /**
+ * Confirms data sections are equal
+ * @param expectedData - just raw data (without sid or size short ints)
+ * @param actualRecordBytes this includes 4 prefix bytes (sid & size)
+ */
+ public static void confirmRecordEncoding(int expectedSid, byte[] expectedData, byte[] actualRecordBytes)
+ throws AssertionFailedError {
+ confirmRecordEncoding(null, expectedSid, expectedData, actualRecordBytes);
+ }
+ /**
+ * Confirms data sections are equal
+ * @param msgPrefix message prefix to be displayed in case of failure
+ * @param expectedData - just raw data (without ushort sid, ushort size)
+ * @param actualRecordBytes this includes 4 prefix bytes (sid & size)
+ */
+ public static void confirmRecordEncoding(String msgPrefix, int expectedSid, byte[] expectedData, byte[] actualRecordBytes)
+ throws AssertionFailedError {
+ int expectedDataSize = expectedData.length;
+ Assert.assertEquals("Size of encode data mismatch", actualRecordBytes.length - 4, expectedDataSize);
+ Assert.assertEquals(expectedSid, LittleEndian.getShort(actualRecordBytes, 0));
+ Assert.assertEquals(expectedDataSize, LittleEndian.getShort(actualRecordBytes, 2));
+ for (int i = 0; i < expectedDataSize; i++)
+ if (expectedData[i] != actualRecordBytes[i+4]) {
+ StringBuilder sb = new StringBuilder(64);
+ if (msgPrefix != null) {
+ sb.append(msgPrefix).append(": ");
+ }
+ sb.append("At offset ").append(i);
+ sb.append(": expected ").append(HexDump.byteToHex(expectedData[i]));
+ sb.append(" but found ").append(HexDump.byteToHex(actualRecordBytes[i+4]));
+ throw new AssertionFailedError(sb.toString());
+ }
+ }
}