Further simplification to RecordInputStream. Mostly regarding Strings, ContinueRecords and LittleEndianInput
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@707802 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
a55f5b5aaa
commit
8f89964d1d
@ -18,7 +18,6 @@
|
|||||||
package org.apache.poi.hssf.record;
|
package org.apache.poi.hssf.record;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
import org.apache.poi.util.LittleEndian;
|
import org.apache.poi.util.LittleEndian;
|
||||||
@ -35,20 +34,37 @@ public final class RecordInputStream extends InputStream implements LittleEndian
|
|||||||
/** Maximum size of a single record (minus the 4 byte header) without a continue*/
|
/** Maximum size of a single record (minus the 4 byte header) without a continue*/
|
||||||
public final static short MAX_RECORD_DATA_SIZE = 8224;
|
public final static short MAX_RECORD_DATA_SIZE = 8224;
|
||||||
private static final int INVALID_SID_VALUE = -1;
|
private static final int INVALID_SID_VALUE = -1;
|
||||||
|
/**
|
||||||
|
* When {@link #_currentDataLength} has this value, it means that the previous BIFF record is
|
||||||
|
* finished, the next sid has been properly read, but the data size field has not been read yet.
|
||||||
|
*/
|
||||||
private static final int DATA_LEN_NEEDS_TO_BE_READ = -1;
|
private static final int DATA_LEN_NEEDS_TO_BE_READ = -1;
|
||||||
private static final byte[] EMPTY_BYTE_ARRAY = { };
|
private static final byte[] EMPTY_BYTE_ARRAY = { };
|
||||||
|
|
||||||
private final InputStream _in;
|
/** {@link LittleEndianInput} facet of the wrapped {@link InputStream} */
|
||||||
/** {@link LittleEndianInput} facet of field {@link #_in} */
|
|
||||||
private final LittleEndianInput _le;
|
private final LittleEndianInput _le;
|
||||||
private int currentSid;
|
/** the record identifier of the BIFF record currently being read */
|
||||||
|
private int _currentSid;
|
||||||
|
/**
|
||||||
|
* Length of the data section of the current BIFF record (always 4 less than the total record size).
|
||||||
|
* When uninitialised, this field is set to {@link #DATA_LEN_NEEDS_TO_BE_READ}.
|
||||||
|
*/
|
||||||
private int _currentDataLength;
|
private int _currentDataLength;
|
||||||
private int nextSid;
|
/**
|
||||||
private int recordOffset;
|
* The BIFF record identifier for the next record is read when just as the current record
|
||||||
private boolean autoContinue; // TODO - remove this
|
* is finished.
|
||||||
|
* This field is only really valid during the time that ({@link #_currentDataLength} ==
|
||||||
|
* {@link #DATA_LEN_NEEDS_TO_BE_READ}). At most other times its value is not really the
|
||||||
|
* 'sid of the next record'. Wwhile mid-record, this field coincidentally holds the sid
|
||||||
|
* of the current record.
|
||||||
|
*/
|
||||||
|
private int _nextSid;
|
||||||
|
/**
|
||||||
|
* index within the data section of the current BIFF record
|
||||||
|
*/
|
||||||
|
private int _currentDataOffset;
|
||||||
|
|
||||||
public RecordInputStream(InputStream in) throws RecordFormatException {
|
public RecordInputStream(InputStream in) throws RecordFormatException {
|
||||||
_in = in;
|
|
||||||
if (in instanceof LittleEndianInput) {
|
if (in instanceof LittleEndianInput) {
|
||||||
// accessing directly is an optimisation
|
// accessing directly is an optimisation
|
||||||
_le = (LittleEndianInput) in;
|
_le = (LittleEndianInput) in;
|
||||||
@ -56,22 +72,20 @@ public final class RecordInputStream extends InputStream implements LittleEndian
|
|||||||
// less optimal, but should work OK just the same. Often occurs in junit tests.
|
// less optimal, but should work OK just the same. Often occurs in junit tests.
|
||||||
_le = new LittleEndianInputStream(in);
|
_le = new LittleEndianInputStream(in);
|
||||||
}
|
}
|
||||||
try {
|
_nextSid = readNextSid();
|
||||||
if (_in.available() < LittleEndian.SHORT_SIZE) {
|
|
||||||
nextSid = INVALID_SID_VALUE;
|
|
||||||
} else {
|
|
||||||
nextSid = LittleEndian.readShort(in);
|
|
||||||
}
|
}
|
||||||
} catch (IOException ex) {
|
|
||||||
throw new RecordFormatException("Error reading bytes", ex);
|
/**
|
||||||
}
|
* @returns the number of bytes available in the current BIFF record
|
||||||
_currentDataLength = DATA_LEN_NEEDS_TO_BE_READ;
|
* @see #remaining()
|
||||||
autoContinue = true;
|
*/
|
||||||
|
public int available() {
|
||||||
|
return remaining();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int read() {
|
public int read() {
|
||||||
checkRecordPosition(LittleEndian.BYTE_SIZE);
|
checkRecordPosition(LittleEndian.BYTE_SIZE);
|
||||||
recordOffset += LittleEndian.BYTE_SIZE;
|
_currentDataOffset += LittleEndian.BYTE_SIZE;
|
||||||
return _le.readUByte();
|
return _le.readUByte();
|
||||||
}
|
}
|
||||||
public int read(byte[] b, int off, int len) {
|
public int read(byte[] b, int off, int len) {
|
||||||
@ -84,32 +98,26 @@ public final class RecordInputStream extends InputStream implements LittleEndian
|
|||||||
}
|
}
|
||||||
|
|
||||||
public short getSid() {
|
public short getSid() {
|
||||||
return (short) currentSid;
|
return (short) _currentSid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public short getLength() { // TODO - remove
|
|
||||||
return (short) _currentDataLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Note - this method is expected to be called only when completed reading the current BIFF record.
|
* Note - this method is expected to be called only when completed reading the current BIFF record.
|
||||||
* Calling this before reaching the end of the current record will cause all remaining data to be
|
* Calling this before reaching the end of the current record will cause all remaining data to be
|
||||||
* discarded
|
* discarded
|
||||||
*/
|
*/
|
||||||
public boolean hasNextRecord() {
|
public boolean hasNextRecord() {
|
||||||
if (_currentDataLength != -1 && _currentDataLength != recordOffset) {
|
if (_currentDataLength != -1 && _currentDataLength != _currentDataOffset) {
|
||||||
System.out.println("WARN. Unread "+remaining()+" bytes of record 0x"+Integer.toHexString(currentSid));
|
System.out.println("WARN. Unread "+remaining()+" bytes of record 0x"+Integer.toHexString(_currentSid));
|
||||||
// discard unread data
|
// discard unread data
|
||||||
while (recordOffset < _currentDataLength) {
|
while (_currentDataOffset < _currentDataLength) {
|
||||||
readByte();
|
readByte();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (_currentDataLength != DATA_LEN_NEEDS_TO_BE_READ) {
|
if (_currentDataLength != DATA_LEN_NEEDS_TO_BE_READ) {
|
||||||
nextSid = readNextSid();
|
_nextSid = readNextSid();
|
||||||
_currentDataLength = DATA_LEN_NEEDS_TO_BE_READ;
|
|
||||||
}
|
}
|
||||||
return nextSid != INVALID_SID_VALUE;
|
return _nextSid != INVALID_SID_VALUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -117,12 +125,7 @@ public final class RecordInputStream extends InputStream implements LittleEndian
|
|||||||
* @return the sid of the next record or {@link #INVALID_SID_VALUE} if at end of stream
|
* @return the sid of the next record or {@link #INVALID_SID_VALUE} if at end of stream
|
||||||
*/
|
*/
|
||||||
private int readNextSid() {
|
private int readNextSid() {
|
||||||
int nAvailable;
|
int nAvailable = _le.available();
|
||||||
try {
|
|
||||||
nAvailable = _in.available();
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RecordFormatException("Error checking stream available bytes", e);
|
|
||||||
}
|
|
||||||
if (nAvailable < EOFRecord.ENCODED_SIZE) {
|
if (nAvailable < EOFRecord.ENCODED_SIZE) {
|
||||||
if (nAvailable > 0) {
|
if (nAvailable > 0) {
|
||||||
// some scrap left over?
|
// some scrap left over?
|
||||||
@ -135,6 +138,7 @@ public final class RecordInputStream extends InputStream implements LittleEndian
|
|||||||
if (result == INVALID_SID_VALUE) {
|
if (result == INVALID_SID_VALUE) {
|
||||||
throw new RecordFormatException("Found invalid sid (" + result + ")");
|
throw new RecordFormatException("Found invalid sid (" + result + ")");
|
||||||
}
|
}
|
||||||
|
_currentDataLength = DATA_LEN_NEEDS_TO_BE_READ;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,12 +147,14 @@ public final class RecordInputStream extends InputStream implements LittleEndian
|
|||||||
* <i>Note: The auto continue flag is reset to true</i>
|
* <i>Note: The auto continue flag is reset to true</i>
|
||||||
*/
|
*/
|
||||||
public void nextRecord() throws RecordFormatException {
|
public void nextRecord() throws RecordFormatException {
|
||||||
if (nextSid == INVALID_SID_VALUE) {
|
if (_nextSid == INVALID_SID_VALUE) {
|
||||||
throw new IllegalStateException("EOF - next record not available");
|
throw new IllegalStateException("EOF - next record not available");
|
||||||
}
|
}
|
||||||
currentSid = nextSid;
|
if (_currentDataLength != DATA_LEN_NEEDS_TO_BE_READ) {
|
||||||
autoContinue = true;
|
throw new IllegalStateException("Cannot call nextRecord() without checking hasNextRecord() first");
|
||||||
recordOffset = 0;
|
}
|
||||||
|
_currentSid = _nextSid;
|
||||||
|
_currentDataOffset = 0;
|
||||||
_currentDataLength = _le.readUShort();
|
_currentDataLength = _le.readUShort();
|
||||||
if (_currentDataLength > MAX_RECORD_DATA_SIZE) {
|
if (_currentDataLength > MAX_RECORD_DATA_SIZE) {
|
||||||
throw new RecordFormatException("The content of an excel record cannot exceed "
|
throw new RecordFormatException("The content of an excel record cannot exceed "
|
||||||
@ -156,19 +162,19 @@ public final class RecordInputStream extends InputStream implements LittleEndian
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAutoContinue(boolean enable) {
|
|
||||||
this.autoContinue = enable;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkRecordPosition(int requiredByteCount) {
|
private void checkRecordPosition(int requiredByteCount) {
|
||||||
|
|
||||||
if (remaining() < requiredByteCount) {
|
int nAvailable = remaining();
|
||||||
if (isContinueNext() && autoContinue) {
|
if (nAvailable >= requiredByteCount) {
|
||||||
|
// all OK
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (nAvailable == 0 && isContinueNext()) {
|
||||||
nextRecord();
|
nextRecord();
|
||||||
} else {
|
return;
|
||||||
throw new ArrayIndexOutOfBoundsException();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
throw new RecordFormatException("Not enough data (" + nAvailable
|
||||||
|
+ ") to read requested (" + requiredByteCount +") bytes");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -176,7 +182,7 @@ public final class RecordInputStream extends InputStream implements LittleEndian
|
|||||||
*/
|
*/
|
||||||
public byte readByte() {
|
public byte readByte() {
|
||||||
checkRecordPosition(LittleEndian.BYTE_SIZE);
|
checkRecordPosition(LittleEndian.BYTE_SIZE);
|
||||||
recordOffset += LittleEndian.BYTE_SIZE;
|
_currentDataOffset += LittleEndian.BYTE_SIZE;
|
||||||
return _le.readByte();
|
return _le.readByte();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,19 +191,19 @@ public final class RecordInputStream extends InputStream implements LittleEndian
|
|||||||
*/
|
*/
|
||||||
public short readShort() {
|
public short readShort() {
|
||||||
checkRecordPosition(LittleEndian.SHORT_SIZE);
|
checkRecordPosition(LittleEndian.SHORT_SIZE);
|
||||||
recordOffset += LittleEndian.SHORT_SIZE;
|
_currentDataOffset += LittleEndian.SHORT_SIZE;
|
||||||
return _le.readShort();
|
return _le.readShort();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int readInt() {
|
public int readInt() {
|
||||||
checkRecordPosition(LittleEndian.INT_SIZE);
|
checkRecordPosition(LittleEndian.INT_SIZE);
|
||||||
recordOffset += LittleEndian.INT_SIZE;
|
_currentDataOffset += LittleEndian.INT_SIZE;
|
||||||
return _le.readInt();
|
return _le.readInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
public long readLong() {
|
public long readLong() {
|
||||||
checkRecordPosition(LittleEndian.LONG_SIZE);
|
checkRecordPosition(LittleEndian.LONG_SIZE);
|
||||||
recordOffset += LittleEndian.LONG_SIZE;
|
_currentDataOffset += LittleEndian.LONG_SIZE;
|
||||||
return _le.readLong();
|
return _le.readLong();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,13 +220,13 @@ public final class RecordInputStream extends InputStream implements LittleEndian
|
|||||||
*/
|
*/
|
||||||
public int readUShort() {
|
public int readUShort() {
|
||||||
checkRecordPosition(LittleEndian.SHORT_SIZE);
|
checkRecordPosition(LittleEndian.SHORT_SIZE);
|
||||||
recordOffset += LittleEndian.SHORT_SIZE;
|
_currentDataOffset += LittleEndian.SHORT_SIZE;
|
||||||
return _le.readUShort();
|
return _le.readUShort();
|
||||||
}
|
}
|
||||||
|
|
||||||
public double readDouble() {
|
public double readDouble() {
|
||||||
checkRecordPosition(LittleEndian.DOUBLE_SIZE);
|
checkRecordPosition(LittleEndian.DOUBLE_SIZE);
|
||||||
recordOffset += LittleEndian.DOUBLE_SIZE;
|
_currentDataOffset += LittleEndian.DOUBLE_SIZE;
|
||||||
long valueLongBits = _le.readLong();
|
long valueLongBits = _le.readLong();
|
||||||
double result = Double.longBitsToDouble(valueLongBits);
|
double result = Double.longBitsToDouble(valueLongBits);
|
||||||
if (Double.isNaN(result)) {
|
if (Double.isNaN(result)) {
|
||||||
@ -235,7 +241,7 @@ public final class RecordInputStream extends InputStream implements LittleEndian
|
|||||||
public void readFully(byte[] buf, int off, int len) {
|
public void readFully(byte[] buf, int off, int len) {
|
||||||
checkRecordPosition(len);
|
checkRecordPosition(len);
|
||||||
_le.readFully(buf, off, len);
|
_le.readFully(buf, off, len);
|
||||||
recordOffset+=len;
|
_currentDataOffset+=len;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String readString() {
|
public String readString() {
|
||||||
@ -369,20 +375,25 @@ public final class RecordInputStream extends InputStream implements LittleEndian
|
|||||||
// already read sid of next record. so current one is finished
|
// already read sid of next record. so current one is finished
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return (_currentDataLength - recordOffset);
|
return _currentDataLength - _currentDataOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @return <code>true</code> when a {@link ContinueRecord} is next.
|
* @return <code>true</code> when a {@link ContinueRecord} is next.
|
||||||
*/
|
*/
|
||||||
public boolean isContinueNext() {
|
private boolean isContinueNext() {
|
||||||
if (_currentDataLength != DATA_LEN_NEEDS_TO_BE_READ && recordOffset != _currentDataLength) {
|
if (_currentDataLength != DATA_LEN_NEEDS_TO_BE_READ && _currentDataOffset != _currentDataLength) {
|
||||||
throw new IllegalStateException("Should never be called before end of current record");
|
throw new IllegalStateException("Should never be called before end of current record");
|
||||||
}
|
}
|
||||||
if (!hasNextRecord()) {
|
if (!hasNextRecord()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return nextSid == ContinueRecord.sid;
|
// At what point are records continued?
|
||||||
|
// - Often from within the char data of long strings (caller is within readStringCommon()).
|
||||||
|
// - From UnicodeString construction (many different points - call via checkRecordPosition)
|
||||||
|
// - During TextObjectRecord construction (just before the text, perhaps within the text,
|
||||||
|
// and before the formatting run data)
|
||||||
|
return _nextSid == ContinueRecord.sid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -84,9 +84,11 @@ public final class SupBookRecord extends Record {
|
|||||||
* @param offset of the record's data (provided a big array of the file)
|
* @param offset of the record's data (provided a big array of the file)
|
||||||
*/
|
*/
|
||||||
public SupBookRecord(RecordInputStream in) {
|
public SupBookRecord(RecordInputStream in) {
|
||||||
|
int recLen = in.remaining();
|
||||||
|
|
||||||
field_1_number_of_sheets = in.readShort();
|
field_1_number_of_sheets = in.readShort();
|
||||||
|
|
||||||
if(in.getLength() > SMALL_RECORD_SIZE) {
|
if(recLen > SMALL_RECORD_SIZE) {
|
||||||
// 5.38.1 External References
|
// 5.38.1 External References
|
||||||
_isAddInFunctions = false;
|
_isAddInFunctions = false;
|
||||||
|
|
||||||
|
@ -131,13 +131,7 @@ public final class TextObjectRecord extends Record {
|
|||||||
_text = new HSSFRichTextString(text);
|
_text = new HSSFRichTextString(text);
|
||||||
|
|
||||||
if (field_7_formattingDataLength > 0) {
|
if (field_7_formattingDataLength > 0) {
|
||||||
if (in.isContinueNext() && in.remaining() == 0) {
|
|
||||||
in.nextRecord();
|
|
||||||
processFontRuns(in, _text, field_7_formattingDataLength);
|
processFontRuns(in, _text, field_7_formattingDataLength);
|
||||||
} else {
|
|
||||||
throw new RecordFormatException(
|
|
||||||
"Expected Continue Record to hold font runs for TextObjectRecord");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,10 +150,6 @@ public final class TextObjectRecord extends Record {
|
|||||||
throw new RecordFormatException("Bad format run data length " + formattingRunDataLength
|
throw new RecordFormatException("Bad format run data length " + formattingRunDataLength
|
||||||
+ ")");
|
+ ")");
|
||||||
}
|
}
|
||||||
if (in.remaining() != formattingRunDataLength) {
|
|
||||||
throw new RecordFormatException("Expected " + formattingRunDataLength
|
|
||||||
+ " bytes but got " + in.remaining());
|
|
||||||
}
|
|
||||||
int nRuns = formattingRunDataLength / FORMAT_RUN_ENCODED_SIZE;
|
int nRuns = formattingRunDataLength / FORMAT_RUN_ENCODED_SIZE;
|
||||||
for (int i = 0; i < nRuns; i++) {
|
for (int i = 0; i < nRuns; i++) {
|
||||||
short index = in.readShort();
|
short index = in.readShort();
|
||||||
|
@ -36,13 +36,8 @@ import java.util.Collections;
|
|||||||
* @author Andrew C. Oliver
|
* @author Andrew C. Oliver
|
||||||
* @author Marc Johnson (mjohnson at apache dot org)
|
* @author Marc Johnson (mjohnson at apache dot org)
|
||||||
* @author Glen Stampoultzis (glens at apache.org)
|
* @author Glen Stampoultzis (glens at apache.org)
|
||||||
* @version 2.0-pre
|
|
||||||
*/
|
*/
|
||||||
|
public final class UnicodeString implements Comparable {
|
||||||
public class UnicodeString
|
|
||||||
implements Comparable
|
|
||||||
{
|
|
||||||
public final static short sid = 0xFFF;
|
|
||||||
private short field_1_charCount; // = 0;
|
private short field_1_charCount; // = 0;
|
||||||
private byte field_2_optionflags; // = 0;
|
private byte field_2_optionflags; // = 0;
|
||||||
private String field_3_string; // = null;
|
private String field_3_string; // = null;
|
||||||
@ -53,8 +48,8 @@ public class UnicodeString
|
|||||||
private static final BitField richText = BitFieldFactory.getInstance(0x8);
|
private static final BitField richText = BitFieldFactory.getInstance(0x8);
|
||||||
|
|
||||||
public static class FormatRun implements Comparable {
|
public static class FormatRun implements Comparable {
|
||||||
private short character;
|
short character;
|
||||||
private short fontIndex;
|
short fontIndex;
|
||||||
|
|
||||||
public FormatRun(short character, short fontIndex) {
|
public FormatRun(short character, short fontIndex) {
|
||||||
this.character = character;
|
this.character = character;
|
||||||
@ -102,15 +97,6 @@ public class UnicodeString
|
|||||||
setString(str);
|
setString(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* construct a unicode string record and fill its fields, ID is ignored
|
|
||||||
* @param in the RecordInputstream to read the record from
|
|
||||||
*/
|
|
||||||
|
|
||||||
public UnicodeString(RecordInputStream in)
|
|
||||||
{
|
|
||||||
fillFields(in); // TODO - inline
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public int hashCode()
|
public int hashCode()
|
||||||
@ -142,9 +128,9 @@ public class UnicodeString
|
|||||||
&& field_3_string.equals(other.field_3_string));
|
&& field_3_string.equals(other.field_3_string));
|
||||||
if (!eq) return false;
|
if (!eq) return false;
|
||||||
|
|
||||||
//Ok string appears to be equal but now lets compare formatting runs
|
//OK string appears to be equal but now lets compare formatting runs
|
||||||
if ((field_4_format_runs == null) && (other.field_4_format_runs == null))
|
if ((field_4_format_runs == null) && (other.field_4_format_runs == null))
|
||||||
//Strings are equal, and there are not formtting runs.
|
//Strings are equal, and there are not formatting runs.
|
||||||
return true;
|
return true;
|
||||||
if (((field_4_format_runs == null) && (other.field_4_format_runs != null)) ||
|
if (((field_4_format_runs == null) && (other.field_4_format_runs != null)) ||
|
||||||
(field_4_format_runs != null) && (other.field_4_format_runs == null))
|
(field_4_format_runs != null) && (other.field_4_format_runs == null))
|
||||||
@ -186,10 +172,10 @@ public class UnicodeString
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* construct a unicode string record and fill its fields, ID is ignored
|
||||||
* @param in the RecordInputstream to read the record from
|
* @param in the RecordInputstream to read the record from
|
||||||
*/
|
*/
|
||||||
protected void fillFields(RecordInputStream in)
|
public UnicodeString(RecordInputStream in) {
|
||||||
{
|
|
||||||
field_1_charCount = in.readShort();
|
field_1_charCount = in.readShort();
|
||||||
field_2_optionflags = in.readByte();
|
field_2_optionflags = in.readByte();
|
||||||
|
|
||||||
@ -206,34 +192,12 @@ public class UnicodeString
|
|||||||
extensionLength = in.readInt();
|
extensionLength = in.readInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
//Now need to get the string data.
|
|
||||||
//Turn off autocontinuation so that we can catch the continue boundary
|
|
||||||
in.setAutoContinue(false);
|
|
||||||
StringBuffer tmpString = new StringBuffer(field_1_charCount);
|
|
||||||
int stringCharCount = field_1_charCount;
|
|
||||||
boolean isCompressed = ((field_2_optionflags & 1) == 0);
|
boolean isCompressed = ((field_2_optionflags & 1) == 0);
|
||||||
while (stringCharCount != 0) {
|
|
||||||
if (in.remaining() == 0) {
|
|
||||||
if (in.isContinueNext()) {
|
|
||||||
in.nextRecord();
|
|
||||||
//Check if we are now reading, compressed or uncompressed unicode.
|
|
||||||
byte optionflags = in.readByte();
|
|
||||||
isCompressed = ((optionflags & 1) == 0);
|
|
||||||
} else
|
|
||||||
throw new RecordFormatException("Expected continue record.");
|
|
||||||
}
|
|
||||||
if (isCompressed) {
|
if (isCompressed) {
|
||||||
char ch = (char)in.readUByte(); // avoid sex
|
field_3_string = in.readCompressedUnicode(field_1_charCount);
|
||||||
tmpString.append(ch);
|
|
||||||
} else {
|
} else {
|
||||||
char ch = (char) in.readShort();
|
field_3_string = in.readUnicodeLEString(field_1_charCount);
|
||||||
tmpString.append(ch);
|
|
||||||
}
|
}
|
||||||
stringCharCount --;
|
|
||||||
}
|
|
||||||
field_3_string = tmpString.toString();
|
|
||||||
//Turn back on autocontinuation
|
|
||||||
in.setAutoContinue(true);
|
|
||||||
|
|
||||||
|
|
||||||
if (isRichText() && (runCount > 0)) {
|
if (isRichText() && (runCount > 0)) {
|
||||||
@ -305,13 +269,8 @@ public class UnicodeString
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get the actual string this contains as a java String object
|
* @return the actual string this contains as a java String object
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return String
|
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public String getString()
|
public String getString()
|
||||||
{
|
{
|
||||||
return field_3_string;
|
return field_3_string;
|
||||||
@ -341,7 +300,7 @@ public class UnicodeString
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (useUTF16)
|
if (useUTF16)
|
||||||
//Set the uncomressed bit
|
//Set the uncompressed bit
|
||||||
field_2_optionflags = highByte.setByte(field_2_optionflags);
|
field_2_optionflags = highByte.setByte(field_2_optionflags);
|
||||||
else field_2_optionflags = highByte.clearByte(field_2_optionflags);
|
else field_2_optionflags = highByte.clearByte(field_2_optionflags);
|
||||||
}
|
}
|
||||||
@ -497,8 +456,8 @@ public class UnicodeString
|
|||||||
|
|
||||||
LittleEndian.putShort(data, offset, ContinueRecord.sid);
|
LittleEndian.putShort(data, offset, ContinueRecord.sid);
|
||||||
offset+=2;
|
offset+=2;
|
||||||
//Record the location of the last continue legnth position, but dont write
|
//Record the location of the last continue length position, but don't write
|
||||||
//anything there yet (since we dont know what it will be!)
|
//anything there yet (since we don't know what it will be!)
|
||||||
stats.lastLengthPos = offset;
|
stats.lastLengthPos = offset;
|
||||||
offset += 2;
|
offset += 2;
|
||||||
|
|
||||||
@ -514,7 +473,6 @@ public class UnicodeString
|
|||||||
|
|
||||||
//Basic string overhead
|
//Basic string overhead
|
||||||
pos = writeContinueIfRequired(stats, 3, pos, data);
|
pos = writeContinueIfRequired(stats, 3, pos, data);
|
||||||
// byte[] retval = new byte[ 3 + (getString().length() * charsize)];
|
|
||||||
LittleEndian.putShort(data, pos, getCharCount());
|
LittleEndian.putShort(data, pos, getCharCount());
|
||||||
pos += 2;
|
pos += 2;
|
||||||
data[ pos ] = getOptionFlags();
|
data[ pos ] = getOptionFlags();
|
||||||
@ -568,39 +526,39 @@ public class UnicodeString
|
|||||||
//Check to see if the offset occurs mid string, if so then we need to add
|
//Check to see if the offset occurs mid string, if so then we need to add
|
||||||
//the byte to start with that represents the first byte of the continue record.
|
//the byte to start with that represents the first byte of the continue record.
|
||||||
if (strSize > stats.remainingSize) {
|
if (strSize > stats.remainingSize) {
|
||||||
//Ok the offset occurs half way through the string, that means that
|
//OK the offset occurs half way through the string, that means that
|
||||||
//we need an extra byte after the continue record ie we didnt finish
|
//we need an extra byte after the continue record ie we didnt finish
|
||||||
//writing out the string the 1st time through
|
//writing out the string the 1st time through
|
||||||
|
|
||||||
//But hang on, how many continue records did we span? What if this is
|
//But hang on, how many continue records did we span? What if this is
|
||||||
//a REALLY long string. We need to work this all out.
|
//a REALLY long string. We need to work this all out.
|
||||||
int ammountThatCantFit = strSize;
|
int amountThatCantFit = strSize;
|
||||||
int strPos = 0;
|
int strPos = 0;
|
||||||
while (ammountThatCantFit > 0) {
|
while (amountThatCantFit > 0) {
|
||||||
int ammountWritten = Math.min(stats.remainingSize, ammountThatCantFit);
|
int amountWritten = Math.min(stats.remainingSize, amountThatCantFit);
|
||||||
//Make sure that the ammount that cant fit takes into account
|
//Make sure that the amount that can't fit takes into account
|
||||||
//whether we are writing double byte unicode
|
//whether we are writing double byte unicode
|
||||||
if (isUncompressedUnicode()) {
|
if (isUncompressedUnicode()) {
|
||||||
//We have the '-1' here because whether this is the first record or
|
//We have the '-1' here because whether this is the first record or
|
||||||
//subsequent continue records, there is always the case that the
|
//subsequent continue records, there is always the case that the
|
||||||
//number of bytes in a string on doube byte boundaries is actually odd.
|
//number of bytes in a string on double byte boundaries is actually odd.
|
||||||
if ( ( (ammountWritten ) % 2) == 1)
|
if ( ( (amountWritten ) % 2) == 1)
|
||||||
ammountWritten--;
|
amountWritten--;
|
||||||
}
|
}
|
||||||
System.arraycopy(strBytes, strPos, data, pos, ammountWritten);
|
System.arraycopy(strBytes, strPos, data, pos, amountWritten);
|
||||||
pos += ammountWritten;
|
pos += amountWritten;
|
||||||
strPos += ammountWritten;
|
strPos += amountWritten;
|
||||||
stats.recordSize += ammountWritten;
|
stats.recordSize += amountWritten;
|
||||||
stats.remainingSize -= ammountWritten;
|
stats.remainingSize -= amountWritten;
|
||||||
|
|
||||||
//Ok lets subtract what we can write
|
//Ok lets subtract what we can write
|
||||||
ammountThatCantFit -= ammountWritten;
|
amountThatCantFit -= amountWritten;
|
||||||
|
|
||||||
//Each iteration of this while loop is another continue record, unless
|
//Each iteration of this while loop is another continue record, unless
|
||||||
//everything now fits.
|
//everything now fits.
|
||||||
if (ammountThatCantFit > 0) {
|
if (amountThatCantFit > 0) {
|
||||||
//We know that a continue WILL be requied, but use this common method
|
//We know that a continue WILL be requied, but use this common method
|
||||||
pos = writeContinueIfRequired(stats, ammountThatCantFit, pos, data);
|
pos = writeContinueIfRequired(stats, amountThatCantFit, pos, data);
|
||||||
|
|
||||||
//The first byte after a continue mid string is the extra byte to
|
//The first byte after a continue mid string is the extra byte to
|
||||||
//indicate if this run is compressed or not.
|
//indicate if this run is compressed or not.
|
||||||
@ -686,7 +644,7 @@ public class UnicodeString
|
|||||||
return highByte.isSet(getOptionFlags());
|
return highByte.isSet(getOptionFlags());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the size of this record, given the ammount of record space
|
/** Returns the size of this record, given the amount of record space
|
||||||
* remaining, it will also include the size of writing a continue record.
|
* remaining, it will also include the size of writing a continue record.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -833,13 +791,6 @@ public class UnicodeString
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public short getSid()
|
|
||||||
{
|
|
||||||
return sid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int compareTo(Object obj)
|
public int compareTo(Object obj)
|
||||||
{
|
{
|
||||||
UnicodeString str = ( UnicodeString ) obj;
|
UnicodeString str = ( UnicodeString ) obj;
|
||||||
@ -877,7 +828,7 @@ public class UnicodeString
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Well the format runs are equal as well!, better check the ExtRst data
|
//Well the format runs are equal as well!, better check the ExtRst data
|
||||||
//Which by the way we dont know how to decode!
|
//Which by the way we don't know how to decode!
|
||||||
if ((field_5_ext_rst == null) && (str.field_5_ext_rst == null))
|
if ((field_5_ext_rst == null) && (str.field_5_ext_rst == null))
|
||||||
return 0;
|
return 0;
|
||||||
if ((field_5_ext_rst == null) && (str.field_5_ext_rst != null))
|
if ((field_5_ext_rst == null) && (str.field_5_ext_rst != null))
|
||||||
|
@ -89,8 +89,10 @@ public final class DocumentInputStream extends InputStream implements LittleEndi
|
|||||||
_currentBlock = getDataInputBlock(0);
|
_currentBlock = getDataInputBlock(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int available() throws IOException {
|
public int available() {
|
||||||
dieIfClosed();
|
if (_closed) {
|
||||||
|
throw new IllegalStateException("cannot perform requested operation on a closed stream");
|
||||||
|
}
|
||||||
return _document_size - _current_offset;
|
return _document_size - _current_offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,7 +196,7 @@ public final class DocumentInputStream extends InputStream implements LittleEndi
|
|||||||
|
|
||||||
private void checkAvaliable(int requestedSize) {
|
private void checkAvaliable(int requestedSize) {
|
||||||
if (_closed) {
|
if (_closed) {
|
||||||
throw new RuntimeException("cannot perform requested operation on a closed stream");
|
throw new IllegalStateException("cannot perform requested operation on a closed stream");
|
||||||
}
|
}
|
||||||
if (requestedSize > _document_size - _current_offset) {
|
if (requestedSize > _document_size - _current_offset) {
|
||||||
throw new RuntimeException("Buffer underrun - requested " + requestedSize
|
throw new RuntimeException("Buffer underrun - requested " + requestedSize
|
||||||
|
@ -40,6 +40,9 @@ public final class LittleEndianByteArrayInputStream implements LittleEndianInput
|
|||||||
this(buf, 0, buf.length);
|
this(buf, 0, buf.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int available() {
|
||||||
|
return _endIndex - _readIndex;
|
||||||
|
}
|
||||||
private void checkPosition(int i) {
|
private void checkPosition(int i) {
|
||||||
if (i > _endIndex - _readIndex) {
|
if (i > _endIndex - _readIndex) {
|
||||||
throw new RuntimeException("Buffer overrun");
|
throw new RuntimeException("Buffer overrun");
|
||||||
|
@ -21,6 +21,7 @@ package org.apache.poi.util;
|
|||||||
* @author Josh Micich
|
* @author Josh Micich
|
||||||
*/
|
*/
|
||||||
public interface LittleEndianInput {
|
public interface LittleEndianInput {
|
||||||
|
int available();
|
||||||
byte readByte();
|
byte readByte();
|
||||||
int readUByte();
|
int readUByte();
|
||||||
short readShort();
|
short readShort();
|
||||||
|
@ -22,6 +22,10 @@ import java.io.IOException;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Wraps an {@link InputStream} providing {@link LittleEndianInput}<p/>
|
||||||
|
*
|
||||||
|
* This class does not buffer any input, so the stream read position maintained
|
||||||
|
* by this class is consistent with that of the inner stream.
|
||||||
*
|
*
|
||||||
* @author Josh Micich
|
* @author Josh Micich
|
||||||
*/
|
*/
|
||||||
@ -29,7 +33,13 @@ public class LittleEndianInputStream extends FilterInputStream implements Little
|
|||||||
public LittleEndianInputStream(InputStream is) {
|
public LittleEndianInputStream(InputStream is) {
|
||||||
super(is);
|
super(is);
|
||||||
}
|
}
|
||||||
|
public int available() {
|
||||||
|
try {
|
||||||
|
return super.available();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
public byte readByte() {
|
public byte readByte() {
|
||||||
return (byte)readUByte();
|
return (byte)readUByte();
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
/* ====================================================================
|
/* ====================================================================
|
||||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
contributor license agreements. See the NOTICE file distributed with
|
contributor license agreements. See the NOTICE file distributed with
|
||||||
@ -16,17 +15,15 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
==================================================================== */
|
==================================================================== */
|
||||||
|
|
||||||
|
|
||||||
package org.apache.poi.poifs.filesystem;
|
package org.apache.poi.poifs.filesystem;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
import java.util.*;
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
import junit.framework.*;
|
|
||||||
|
|
||||||
import org.apache.poi.poifs.property.DirectoryProperty;
|
import org.apache.poi.poifs.property.DirectoryProperty;
|
||||||
import org.apache.poi.poifs.property.DocumentProperty;
|
|
||||||
import org.apache.poi.poifs.storage.RawDataBlock;
|
import org.apache.poi.poifs.storage.RawDataBlock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -35,22 +32,9 @@ import org.apache.poi.poifs.storage.RawDataBlock;
|
|||||||
* @author Marc Johnson
|
* @author Marc Johnson
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class TestDocumentInputStream
|
public final class TestDocumentInputStream extends TestCase {
|
||||||
extends TestCase
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
protected void setUp() throws Exception {
|
||||||
* Constructor TestDocumentInputStream
|
|
||||||
*
|
|
||||||
* @param name
|
|
||||||
*
|
|
||||||
* @exception IOException
|
|
||||||
*/
|
|
||||||
|
|
||||||
public TestDocumentInputStream(String name)
|
|
||||||
throws IOException
|
|
||||||
{
|
|
||||||
super(name);
|
|
||||||
int blocks = (_workbook_size + 511) / 512;
|
int blocks = (_workbook_size + 511) / 512;
|
||||||
|
|
||||||
_workbook_data = new byte[ 512 * blocks ];
|
_workbook_data = new byte[ 512 * blocks ];
|
||||||
@ -86,13 +70,8 @@ public class TestDocumentInputStream
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* test constructor
|
* test constructor
|
||||||
*
|
|
||||||
* @exception IOException
|
|
||||||
*/
|
*/
|
||||||
|
public void testConstructor() throws IOException {
|
||||||
public void testConstructor()
|
|
||||||
throws IOException
|
|
||||||
{
|
|
||||||
DocumentInputStream stream = new DocumentInputStream(_workbook);
|
DocumentInputStream stream = new DocumentInputStream(_workbook);
|
||||||
|
|
||||||
assertEquals(_workbook_size, stream.available());
|
assertEquals(_workbook_size, stream.available());
|
||||||
@ -100,13 +79,8 @@ public class TestDocumentInputStream
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* test available() behavior
|
* test available() behavior
|
||||||
*
|
|
||||||
* @exception IOException
|
|
||||||
*/
|
*/
|
||||||
|
public void testAvailable() throws IOException {
|
||||||
public void testAvailable()
|
|
||||||
throws IOException
|
|
||||||
{
|
|
||||||
DocumentInputStream stream = new DocumentInputStream(_workbook);
|
DocumentInputStream stream = new DocumentInputStream(_workbook);
|
||||||
|
|
||||||
assertEquals(_workbook_size, stream.available());
|
assertEquals(_workbook_size, stream.available());
|
||||||
@ -115,9 +89,7 @@ public class TestDocumentInputStream
|
|||||||
{
|
{
|
||||||
stream.available();
|
stream.available();
|
||||||
fail("Should have caught IOException");
|
fail("Should have caught IOException");
|
||||||
}
|
} catch (IllegalStateException ignored) {
|
||||||
catch (IOException ignored)
|
|
||||||
{
|
|
||||||
|
|
||||||
// as expected
|
// as expected
|
||||||
}
|
}
|
||||||
@ -125,13 +97,8 @@ public class TestDocumentInputStream
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* test mark/reset/markSupported.
|
* test mark/reset/markSupported.
|
||||||
*
|
|
||||||
* @exception IOException
|
|
||||||
*/
|
*/
|
||||||
|
public void testMarkFunctions() throws IOException {
|
||||||
public void testMarkFunctions()
|
|
||||||
throws IOException
|
|
||||||
{
|
|
||||||
DocumentInputStream stream = new DocumentInputStream(_workbook);
|
DocumentInputStream stream = new DocumentInputStream(_workbook);
|
||||||
byte[] buffer = new byte[ _workbook_size / 5 ];
|
byte[] buffer = new byte[ _workbook_size / 5 ];
|
||||||
|
|
||||||
@ -169,13 +136,8 @@ public class TestDocumentInputStream
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* test simple read method
|
* test simple read method
|
||||||
*
|
|
||||||
* @exception IOException
|
|
||||||
*/
|
*/
|
||||||
|
public void testReadSingleByte() throws IOException {
|
||||||
public void testReadSingleByte()
|
|
||||||
throws IOException
|
|
||||||
{
|
|
||||||
DocumentInputStream stream = new DocumentInputStream(_workbook);
|
DocumentInputStream stream = new DocumentInputStream(_workbook);
|
||||||
int remaining = _workbook_size;
|
int remaining = _workbook_size;
|
||||||
|
|
||||||
@ -205,13 +167,8 @@ public class TestDocumentInputStream
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Test buffered read
|
* Test buffered read
|
||||||
*
|
|
||||||
* @exception IOException
|
|
||||||
*/
|
*/
|
||||||
|
public void testBufferRead() throws IOException {
|
||||||
public void testBufferRead()
|
|
||||||
throws IOException
|
|
||||||
{
|
|
||||||
DocumentInputStream stream = new DocumentInputStream(_workbook);
|
DocumentInputStream stream = new DocumentInputStream(_workbook);
|
||||||
|
|
||||||
try
|
try
|
||||||
@ -275,13 +232,8 @@ public class TestDocumentInputStream
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Test complex buffered read
|
* Test complex buffered read
|
||||||
*
|
|
||||||
* @exception IOException
|
|
||||||
*/
|
*/
|
||||||
|
public void testComplexBufferRead() throws IOException {
|
||||||
public void testComplexBufferRead()
|
|
||||||
throws IOException
|
|
||||||
{
|
|
||||||
DocumentInputStream stream = new DocumentInputStream(_workbook);
|
DocumentInputStream stream = new DocumentInputStream(_workbook);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -387,13 +339,8 @@ public class TestDocumentInputStream
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* test skip
|
* test skip
|
||||||
*
|
|
||||||
* @exception IOException
|
|
||||||
*/
|
*/
|
||||||
|
public void testSkip() throws IOException {
|
||||||
public void testSkip()
|
|
||||||
throws IOException
|
|
||||||
{
|
|
||||||
DocumentInputStream stream = new DocumentInputStream(_workbook);
|
DocumentInputStream stream = new DocumentInputStream(_workbook);
|
||||||
|
|
||||||
assertEquals(_workbook_size, stream.available());
|
assertEquals(_workbook_size, stream.available());
|
||||||
@ -418,17 +365,4 @@ public class TestDocumentInputStream
|
|||||||
stream.skip(2 + ( long ) Integer.MAX_VALUE));
|
stream.skip(2 + ( long ) Integer.MAX_VALUE));
|
||||||
assertEquals(0, stream.available());
|
assertEquals(0, stream.available());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* main method to run the unit tests
|
|
||||||
*
|
|
||||||
* @param ignored_args
|
|
||||||
*/
|
|
||||||
|
|
||||||
public static void main(String [] ignored_args)
|
|
||||||
{
|
|
||||||
System.out.println(
|
|
||||||
"Testing org.apache.poi.poifs.filesystem.DocumentInputStream");
|
|
||||||
junit.textui.TestRunner.run(TestDocumentInputStream.class);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user