git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1771583 13f79535-47bb-0310-9956-ffa450edef68tags/REL_3_16_BETA2
@@ -17,6 +17,8 @@ | |||
package org.apache.poi.hpsf; | |||
import java.util.Arrays; | |||
import org.apache.poi.util.HexDump; | |||
import org.apache.poi.util.StringUtil; | |||
@@ -40,12 +42,15 @@ public class ClassID | |||
public static final ClassID POWERPOINT95 = new ClassID("{EA7BAE70-FB3B-11CD-A903-00AA00510EA3}"); | |||
public static final ClassID EQUATION30 = new ClassID("{0002CE02-0000-0000-C000-000000000046}"); | |||
/** <p>The number of bytes occupied by this object in the byte | |||
* stream.</p> */ | |||
public static final int LENGTH = 16; | |||
/** | |||
* <p>The bytes making out the class ID in correct order, | |||
* i.e. big-endian.</p> | |||
*/ | |||
protected byte[] bytes; | |||
private final byte[] bytes = new byte[LENGTH]; | |||
@@ -56,8 +61,7 @@ public class ClassID | |||
* @param src The byte array to read from. | |||
* @param offset The offset of the first byte to read. | |||
*/ | |||
public ClassID(final byte[] src, final int offset) | |||
{ | |||
public ClassID(final byte[] src, final int offset) { | |||
read(src, offset); | |||
} | |||
@@ -66,11 +70,8 @@ public class ClassID | |||
* <p>Creates a {@link ClassID} and initializes its value with | |||
* 0x00 bytes.</p> | |||
*/ | |||
public ClassID() | |||
{ | |||
bytes = new byte[LENGTH]; | |||
for (int i = 0; i < LENGTH; i++) | |||
bytes[i] = 0x00; | |||
public ClassID() { | |||
Arrays.fill(bytes, (byte)0); | |||
} | |||
@@ -81,7 +82,6 @@ public class ClassID | |||
* @param externalForm representation of the Class ID represented by this object. | |||
*/ | |||
public ClassID(String externalForm) { | |||
bytes = new byte[LENGTH]; | |||
String clsStr = externalForm.replaceAll("[{}-]", ""); | |||
for (int i=0; i<clsStr.length(); i+=2) { | |||
bytes[i/2] = (byte)Integer.parseInt(clsStr.substring(i, i+2), 16); | |||
@@ -89,16 +89,11 @@ public class ClassID | |||
} | |||
/** <p>The number of bytes occupied by this object in the byte | |||
* stream.</p> */ | |||
public static final int LENGTH = 16; | |||
/** | |||
* @return The number of bytes occupied by this object in the byte | |||
* stream. | |||
*/ | |||
public int length() | |||
{ | |||
public int length() { | |||
return LENGTH; | |||
} | |||
@@ -110,8 +105,7 @@ public class ClassID | |||
* | |||
* @return the bytes making out the class ID. | |||
*/ | |||
public byte[] getBytes() | |||
{ | |||
public byte[] getBytes() { | |||
return bytes; | |||
} | |||
@@ -123,10 +117,8 @@ public class ClassID | |||
* @param bytes The bytes making out the class ID in big-endian format. They | |||
* are copied without their order being changed. | |||
*/ | |||
public void setBytes(final byte[] bytes) | |||
{ | |||
for (int i = 0; i < this.bytes.length; i++) | |||
this.bytes[i] = bytes[i]; | |||
public void setBytes(final byte[] bytes) { | |||
System.arraycopy(bytes, 0, this.bytes, 0, LENGTH); | |||
} | |||
@@ -141,10 +133,7 @@ public class ClassID | |||
* | |||
* @return A byte array containing the class ID. | |||
*/ | |||
public byte[] read(final byte[] src, final int offset) | |||
{ | |||
bytes = new byte[16]; | |||
public byte[] read(final byte[] src, final int offset) { | |||
/* Read double word. */ | |||
bytes[0] = src[3 + offset]; | |||
bytes[1] = src[2 + offset]; | |||
@@ -160,8 +149,7 @@ public class ClassID | |||
bytes[7] = src[6 + offset]; | |||
/* Read 8 bytes. */ | |||
for (int i = 8; i < 16; i++) | |||
bytes[i] = src[i + offset]; | |||
System.arraycopy(src, 8 + offset, bytes, 8, 8); | |||
return bytes; | |||
} | |||
@@ -180,13 +168,14 @@ public class ClassID | |||
* ID 16 bytes in the byte array after the <var>offset</var> position. | |||
*/ | |||
public void write(final byte[] dst, final int offset) | |||
throws ArrayStoreException | |||
{ | |||
throws ArrayStoreException { | |||
/* Check array size: */ | |||
if (dst.length < 16) | |||
if (dst.length < LENGTH) { | |||
throw new ArrayStoreException | |||
("Destination byte[] must have room for at least 16 bytes, " + | |||
"but has a length of only " + dst.length + "."); | |||
} | |||
/* Write double word. */ | |||
dst[0 + offset] = bytes[3]; | |||
dst[1 + offset] = bytes[2]; | |||
@@ -202,8 +191,7 @@ public class ClassID | |||
dst[7 + offset] = bytes[6]; | |||
/* Write 8 bytes. */ | |||
for (int i = 8; i < 16; i++) | |||
dst[i + offset] = bytes[i]; | |||
System.arraycopy(bytes, 8, dst, 8 + offset, 8); | |||
} | |||
@@ -217,16 +205,19 @@ public class ClassID | |||
* <code>false</code>. | |||
*/ | |||
@Override | |||
public boolean equals(final Object o) | |||
{ | |||
if (o == null || !(o instanceof ClassID)) | |||
public boolean equals(final Object o) { | |||
if (o == null || !(o instanceof ClassID)) { | |||
return false; | |||
} | |||
final ClassID cid = (ClassID) o; | |||
if (bytes.length != cid.bytes.length) | |||
if (bytes.length != cid.bytes.length) { | |||
return false; | |||
for (int i = 0; i < bytes.length; i++) | |||
if (bytes[i] != cid.bytes[i]) | |||
} | |||
for (int i = 0; i < bytes.length; i++) { | |||
if (bytes[i] != cid.bytes[i]) { | |||
return false; | |||
} | |||
} | |||
return true; | |||
} | |||
@@ -236,8 +227,7 @@ public class ClassID | |||
* @see Object#hashCode() | |||
*/ | |||
@Override | |||
public int hashCode() | |||
{ | |||
public int hashCode() { | |||
return new String(bytes, StringUtil.UTF8).hashCode(); | |||
} | |||
@@ -248,18 +238,16 @@ public class ClassID | |||
* @return String representation of the Class ID represented by this object. | |||
*/ | |||
@Override | |||
public String toString() | |||
{ | |||
StringBuffer sbClassId = new StringBuffer(38); | |||
public String toString() { | |||
StringBuilder sbClassId = new StringBuilder(38); | |||
sbClassId.append('{'); | |||
for (int i = 0; i < 16; i++) | |||
{ | |||
for (int i = 0; i < LENGTH; i++) { | |||
sbClassId.append(HexDump.toHex(bytes[i])); | |||
if (i == 3 || i == 5 || i == 7 || i == 9) | |||
if (i == 3 || i == 5 || i == 7 || i == 9) { | |||
sbClassId.append('-'); | |||
} | |||
} | |||
sbClassId.append('}'); | |||
return sbClassId.toString(); | |||
} | |||
} |
@@ -30,35 +30,35 @@ import java.io.OutputStream; | |||
* TODO Fix this, as it starts to go wrong on | |||
* large streams | |||
*/ | |||
final class HDGFLZWCompressor { | |||
/* package */ final class HDGFLZWCompressor { | |||
// We use 12 bit codes: | |||
// * 0-255 are real bytes | |||
// * 256-4095 are the substring codes | |||
// Java handily initialises our buffer / dictionary | |||
// to all zeros | |||
byte[] dict = new byte[4096]; | |||
private byte[] dict = new byte[4096]; | |||
// The next block of data to be written out, minus | |||
// its mask byte | |||
byte[] buffer = new byte[16]; | |||
private byte[] buffer = new byte[16]; | |||
// And how long it is | |||
// (Un-compressed codes are 1 byte each, compressed codes | |||
// are two) | |||
int bufferLen = 0; | |||
private int bufferLen = 0; | |||
// The raw length of a code is limited to 4 bits + 2 | |||
byte[] rawCode = new byte[18]; | |||
private byte[] rawCode = new byte[18]; | |||
// And how much we're using | |||
int rawCodeLen = 0; | |||
private int rawCodeLen = 0; | |||
// How far through the input and output streams we are | |||
int posInp = 0; | |||
int posOut = 0; | |||
private int posInp = 0; | |||
private int posOut = 0; | |||
// What the next mask byte to output will be | |||
int nextMask = 0; | |||
private int nextMask = 0; | |||
// And how many bits we've already set | |||
int maskBitsSet = 0; | |||
private int maskBitsSet = 0; | |||
public HDGFLZWCompressor() {} | |||
@@ -95,7 +95,7 @@ public final class Chunk { | |||
public int getOnDiskSize() { | |||
int size = header.getSizeInBytes() + contents.length; | |||
if(trailer != null) { | |||
size += trailer.trailerData.length; | |||
size += trailer.getTrailerData().length; | |||
} | |||
if(separator != null) { | |||
size += separator.separatorData.length; |
@@ -130,7 +130,7 @@ public final class ChunkFactory { | |||
ChunkHeader header = | |||
ChunkHeader.createChunkHeader(version, data, offset); | |||
// Sanity check | |||
if(header.length < 0) { | |||
if(header.getLength() < 0) { | |||
throw new IllegalArgumentException("Found a chunk with a negative length, which isn't allowed"); | |||
} | |||
@@ -144,14 +144,14 @@ public final class ChunkFactory { | |||
"Header called for " + header.getLength() +" bytes, but that would take us past the end of the data!"); | |||
endOfDataPos = data.length; | |||
header.length = data.length - offset - header.getSizeInBytes(); | |||
header.setLength(data.length - offset - header.getSizeInBytes()); | |||
if(header.hasTrailer()) { | |||
header.length -= 8; | |||
header.setLength(header.getLength() - 8); | |||
endOfDataPos -= 8; | |||
} | |||
if(header.hasSeparator()) { | |||
header.length -= 4; | |||
header.setLength(header.getLength() - 4); | |||
endOfDataPos -= 4; | |||
} | |||
} |
@@ -25,10 +25,10 @@ import java.nio.charset.Charset; | |||
* A chunk header | |||
*/ | |||
public abstract class ChunkHeader { | |||
protected int type; | |||
protected int id; | |||
protected int length; | |||
protected int unknown1; | |||
private int type; | |||
private int id; | |||
private int length; | |||
private int unknown1; | |||
/** | |||
* Creates the appropriate ChunkHeader for the Chunk Header at | |||
@@ -42,23 +42,23 @@ public abstract class ChunkHeader { | |||
} else { | |||
ch = new ChunkHeaderV6(); | |||
} | |||
ch.type = (int)LittleEndian.getUInt(data, offset + 0); | |||
ch.id = (int)LittleEndian.getUInt(data, offset + 4); | |||
ch.unknown1 = (int)LittleEndian.getUInt(data, offset + 8); | |||
ch.length = (int)LittleEndian.getUInt(data, offset + 12); | |||
ch.unknown2 = LittleEndian.getShort(data, offset + 16); | |||
ch.unknown3 = LittleEndian.getUByte(data, offset + 18); | |||
ch.setType((int)LittleEndian.getUInt(data, offset + 0)); | |||
ch.setId((int)LittleEndian.getUInt(data, offset + 4)); | |||
ch.setUnknown1((int)LittleEndian.getUInt(data, offset + 8)); | |||
ch.setLength((int)LittleEndian.getUInt(data, offset + 12)); | |||
ch.setUnknown2(LittleEndian.getShort(data, offset + 16)); | |||
ch.setUnknown3(LittleEndian.getUByte(data, offset + 18)); | |||
return ch; | |||
} else if(documentVersion == 5 || documentVersion == 4) { | |||
ChunkHeaderV4V5 ch = new ChunkHeaderV4V5(); | |||
ch.type = LittleEndian.getShort(data, offset + 0); | |||
ch.id = LittleEndian.getShort(data, offset + 2); | |||
ch.unknown2 = LittleEndian.getUByte(data, offset + 4); | |||
ch.unknown3 = LittleEndian.getUByte(data, offset + 5); | |||
ch.unknown1 = LittleEndian.getShort(data, offset + 6); | |||
ch.length = (int)LittleEndian.getUInt(data, offset + 8); | |||
ch.setType(LittleEndian.getShort(data, offset + 0)); | |||
ch.setId(LittleEndian.getShort(data, offset + 2)); | |||
ch.setUnknown2(LittleEndian.getUByte(data, offset + 4)); | |||
ch.setUnknown3(LittleEndian.getUByte(data, offset + 5)); | |||
ch.setUnknown1(LittleEndian.getShort(data, offset + 6)); | |||
ch.setLength((int)LittleEndian.getUInt(data, offset + 8)); | |||
return ch; | |||
} else { | |||
@@ -90,6 +90,7 @@ public abstract class ChunkHeader { | |||
public int getId() { | |||
return id; | |||
} | |||
/** | |||
* Returns the length of the trunk, excluding the length | |||
* of the header, trailer or separator. | |||
@@ -97,6 +98,7 @@ public abstract class ChunkHeader { | |||
public int getLength() { | |||
return length; | |||
} | |||
/** | |||
* Returns the type of the chunk, which affects the | |||
* mandatory information | |||
@@ -104,7 +106,24 @@ public abstract class ChunkHeader { | |||
public int getType() { | |||
return type; | |||
} | |||
public int getUnknown1() { | |||
return unknown1; | |||
} | |||
void setType(int type) { | |||
this.type = type; | |||
} | |||
void setId(int id) { | |||
this.id = id; | |||
} | |||
void setLength(int length) { | |||
this.length = length; | |||
} | |||
void setUnknown1(int unknown1) { | |||
this.unknown1 = unknown1; | |||
} | |||
} |
@@ -23,30 +23,45 @@ import java.nio.charset.Charset; | |||
* A chunk header from v11+ | |||
*/ | |||
public final class ChunkHeaderV11 extends ChunkHeaderV6 { | |||
/** | |||
* Does the chunk have a separator? | |||
*/ | |||
public boolean hasSeparator() { | |||
// For some reason, there are two types that don't have a | |||
// separator despite the flags that indicate they do | |||
if(type == 0x1f || type == 0xc9) { return false; } | |||
// If there's a trailer, there's a separator | |||
if(hasTrailer()) { return true; } | |||
if(unknown2 == 2 && unknown3 == 0x55) { return true; } | |||
if(unknown2 == 2 && unknown3 == 0x54 && type == 0xa9) { return true; } | |||
if(unknown2 == 2 && unknown3 == 0x54 && type == 0xaa) { return true; } | |||
if(unknown2 == 2 && unknown3 == 0x54 && type == 0xb4) { return true; } | |||
if(unknown2 == 2 && unknown3 == 0x54 && type == 0xb6) { return true; } | |||
if(unknown2 == 3 && unknown3 != 0x50) { return true; } | |||
if(type == 0x69) { return true; } | |||
return false; | |||
} | |||
@Override | |||
public Charset getChunkCharset() { | |||
return Charset.forName("UTF-16LE"); | |||
} | |||
/** | |||
* Does the chunk have a separator? | |||
*/ | |||
public boolean hasSeparator() { | |||
short unknown2 = getUnknown2(); | |||
short unknown3 = getUnknown3(); | |||
switch (getType()) { | |||
case 0x1f: case 0xc9: | |||
// For some reason, there are two types that don't have a | |||
// separator despite the flags that indicate they do | |||
return false; | |||
case 0x69: | |||
return true; | |||
case 0xa9: case 0xaa: case 0xb4: case 0xb6: | |||
if (unknown2 == 2 && unknown3 == 0x54) { | |||
return true; | |||
} | |||
break; | |||
default: | |||
break; | |||
} | |||
if ( | |||
(unknown2 == 2 && unknown3 == 0x55) || | |||
(unknown2 == 3 && unknown3 != 0x50) | |||
) { | |||
return true; | |||
} | |||
// If there's a trailer, there's a separator | |||
return hasTrailer(); | |||
} | |||
@Override | |||
public Charset getChunkCharset() { | |||
return Charset.forName("UTF-16LE"); | |||
} | |||
} |
@@ -23,8 +23,8 @@ import java.nio.charset.Charset; | |||
* A chunk header from v4 or v5 | |||
*/ | |||
public final class ChunkHeaderV4V5 extends ChunkHeader { | |||
protected short unknown2; | |||
protected short unknown3; | |||
private short unknown2; | |||
private short unknown3; | |||
public short getUnknown2() { | |||
return unknown2; | |||
@@ -61,4 +61,12 @@ public final class ChunkHeaderV4V5 extends ChunkHeader { | |||
public Charset getChunkCharset() { | |||
return Charset.forName("ASCII"); | |||
} | |||
void setUnknown2(short unknown2) { | |||
this.unknown2 = unknown2; | |||
} | |||
void setUnknown3(short unknown3) { | |||
this.unknown3 = unknown3; | |||
} | |||
} |
@@ -23,8 +23,8 @@ import java.nio.charset.Charset; | |||
* A chunk header from v6 | |||
*/ | |||
public class ChunkHeaderV6 extends ChunkHeader { | |||
protected short unknown2; | |||
protected short unknown3; | |||
private short unknown2; | |||
private short unknown3; | |||
public short getUnknown2() { | |||
return unknown2; | |||
@@ -45,15 +45,15 @@ public class ChunkHeaderV6 extends ChunkHeader { | |||
* Does the chunk have a trailer? | |||
*/ | |||
public boolean hasTrailer() { | |||
if(unknown1 != 0 || type == 0x71 || type == 0x70) { | |||
return true; | |||
} | |||
if(type == 0x6b || type == 0x6a || type == 0x69 || type == 0x66 | |||
|| type == 0x65 || type == 0x2c) { | |||
return true; | |||
} | |||
return false; | |||
switch (getType()) { | |||
case 0x2c: case 0x65: case 0x66: case 0x69: | |||
case 0x6a: case 0x6b: case 0x70: case 0x71: | |||
return true; | |||
default: | |||
return (getUnknown1() != 0); | |||
} | |||
} | |||
/** | |||
* Does the chunk have a separator? | |||
*/ | |||
@@ -66,4 +66,12 @@ public class ChunkHeaderV6 extends ChunkHeader { | |||
public Charset getChunkCharset() { | |||
return Charset.forName("ASCII"); | |||
} | |||
void setUnknown2(short unknown2) { | |||
this.unknown2 = unknown2; | |||
} | |||
void setUnknown3(short unknown3) { | |||
this.unknown3 = unknown3; | |||
} | |||
} |
@@ -21,7 +21,7 @@ package org.apache.poi.hdgf.chunks; | |||
* A trailer that follows a chunk | |||
*/ | |||
public final class ChunkTrailer { | |||
protected byte[] trailerData; | |||
private byte[] trailerData; | |||
public ChunkTrailer(byte[] data, int offset) { | |||
trailerData = new byte[8]; | |||
@@ -31,4 +31,8 @@ public final class ChunkTrailer { | |||
public String toString() { | |||
return "<ChunkTrailer of length " + trailerData.length + ">"; | |||
} | |||
byte[] getTrailerData() { | |||
return trailerData; | |||
} | |||
} |
@@ -37,8 +37,8 @@ import org.apache.poi.poifs.filesystem.NPOIFSFileSystem; | |||
public final class VSDDumper { | |||
final static String tabs = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"; | |||
final PrintStream ps; | |||
final HDGFDiagram hdgf; | |||
private final PrintStream ps; | |||
private final HDGFDiagram hdgf; | |||
VSDDumper(PrintStream ps, HDGFDiagram hdgf) { | |||
this.ps = ps; | |||
this.hdgf = hdgf; |
@@ -22,11 +22,11 @@ package org.apache.poi.hdgf.pointers; | |||
* blocks elsewhere in the file | |||
*/ | |||
public abstract class Pointer { | |||
protected int type; | |||
protected int address; | |||
protected int offset; | |||
protected int length; | |||
protected short format; | |||
private int type; | |||
private int address; | |||
private int offset; | |||
private int length; | |||
private short format; | |||
public int getAddress() { | |||
return address; | |||
@@ -53,4 +53,25 @@ public abstract class Pointer { | |||
public abstract boolean destinationHasPointers(); | |||
public abstract boolean destinationHasChunks(); | |||
public abstract boolean destinationCompressed(); | |||
protected void setType(int type) { | |||
this.type = type; | |||
} | |||
protected void setAddress(int address) { | |||
this.address = address; | |||
} | |||
protected void setOffset(int offset) { | |||
this.offset = offset; | |||
} | |||
protected void setLength(int length) { | |||
this.length = length; | |||
} | |||
protected void setFormat(short format) { | |||
this.format = format; | |||
} | |||
protected boolean isFormatBetween(int min, int max) { | |||
return (min <= format && format < max); | |||
} | |||
} |
@@ -38,20 +38,20 @@ public final class PointerFactory { | |||
Pointer p; | |||
if(version >= 6) { | |||
p = new PointerV6(); | |||
p.type = LittleEndian.getInt(data, offset+0); | |||
p.address = (int)LittleEndian.getUInt(data, offset+4); | |||
p.offset = (int)LittleEndian.getUInt(data, offset+8); | |||
p.length = (int)LittleEndian.getUInt(data, offset+12); | |||
p.format = LittleEndian.getShort(data, offset+16); | |||
p.setType(LittleEndian.getInt(data, offset+0)); | |||
p.setAddress((int)LittleEndian.getUInt(data, offset+4)); | |||
p.setOffset((int)LittleEndian.getUInt(data, offset+8)); | |||
p.setLength((int)LittleEndian.getUInt(data, offset+12)); | |||
p.setFormat(LittleEndian.getShort(data, offset+16)); | |||
return p; | |||
} else if(version == 5) { | |||
p = new PointerV5(); | |||
p.type = LittleEndian.getShort(data, offset+0); | |||
p.format = LittleEndian.getShort(data, offset+2); | |||
p.address = (int)LittleEndian.getUInt(data, offset+4); | |||
p.offset = (int)LittleEndian.getUInt(data, offset+8); | |||
p.length = (int)LittleEndian.getUInt(data, offset+12); | |||
p.setType(LittleEndian.getShort(data, offset+0)); | |||
p.setFormat(LittleEndian.getShort(data, offset+2)); | |||
p.setAddress((int)LittleEndian.getUInt(data, offset+4)); | |||
p.setOffset((int)LittleEndian.getUInt(data, offset+8)); | |||
p.setLength((int)LittleEndian.getUInt(data, offset+12)); | |||
return p; | |||
} else { |
@@ -25,33 +25,34 @@ import org.apache.poi.util.LittleEndian; | |||
public final class PointerV5 extends Pointer { | |||
// TODO Are these getters correct? | |||
public boolean destinationHasStrings() { | |||
return (0x40 <= format && format < 0x50); | |||
return isFormatBetween(0x40, 0x50); | |||
} | |||
public boolean destinationHasPointers() { | |||
if(type == 20) { | |||
return true; | |||
switch (getType()) { | |||
case 20: | |||
return true; | |||
case 22: | |||
return false; | |||
default: | |||
return isFormatBetween(0x1d, 0x1f) || isFormatBetween(0x50, 0x60); | |||
} | |||
if(type == 22) { | |||
return false; | |||
} | |||
if(format == 0x1d || format == 0x1e) { | |||
return true; | |||
} | |||
return (0x50 <= format && format < 0x60); | |||
} | |||
public boolean destinationHasChunks() { | |||
if (type == 21) { | |||
return true; | |||
} | |||
if (type == 24) { | |||
return true; | |||
switch (getType()) { | |||
case 21: | |||
return true; | |||
case 24: | |||
return true; | |||
default: | |||
return isFormatBetween(0xd0, 0xdf); | |||
} | |||
return (0xd0 <= format && format < 0xdf); | |||
} | |||
public boolean destinationCompressed() { | |||
// Apparently, it's the second least significant bit | |||
return (format & 2) > 0; | |||
return (getFormat() & 2) > 0; | |||
} | |||
/** | |||
@@ -63,7 +64,7 @@ public final class PointerV5 extends Pointer { | |||
* Depends on the type only, not stored | |||
*/ | |||
public int getNumPointersOffset(byte[] data) { | |||
switch (type) { | |||
switch (getType()) { | |||
case 0x1d: | |||
case 0x4e: | |||
return 30; |
@@ -24,20 +24,19 @@ import org.apache.poi.util.LittleEndian; | |||
*/ | |||
public final class PointerV6 extends Pointer { | |||
public boolean destinationHasStrings() { | |||
return (0x40 <= format && format < 0x50); | |||
return isFormatBetween(0x40, 0x50); | |||
} | |||
public boolean destinationHasPointers() { | |||
if(type == 20) return true; | |||
if(format == 0x1d || format == 0x1e) return true; | |||
return (0x50 <= format && format < 0x60); | |||
return (getType() == 20) || isFormatBetween(0x1d, 0x1f) || isFormatBetween(0x50, 0x60); | |||
} | |||
public boolean destinationHasChunks() { | |||
return (0xd0 <= format && format < 0xdf); | |||
return isFormatBetween(0xd0, 0xdf); | |||
} | |||
public boolean destinationCompressed() { | |||
// Apparently, it's the second least significant bit | |||
return (format & 2) > 0; | |||
return (getFormat() & 2) > 0; | |||
} | |||
/** |
@@ -42,6 +42,7 @@ public abstract class EscherPart extends HPBFPart { | |||
new DefaultEscherRecordFactory(); | |||
ArrayList<EscherRecord> ec = new ArrayList<EscherRecord>(); | |||
byte data[] = getData(); | |||
int left = data.length; | |||
while(left > 0) { | |||
EscherRecord er = erf.createRecord(data, 0); | |||
@@ -68,12 +69,14 @@ public abstract class EscherPart extends HPBFPart { | |||
size += records[i].getRecordSize(); | |||
} | |||
data = new byte[size]; | |||
byte data[] = new byte[size]; | |||
size = 0; | |||
for(int i=0; i<records.length; i++) { | |||
int thisSize = | |||
records[i].serialize(size, data); | |||
size += thisSize; | |||
} | |||
setData(data); | |||
} | |||
} |
@@ -31,7 +31,7 @@ import org.apache.poi.util.IOUtils; | |||
* for all of them. | |||
*/ | |||
public abstract class HPBFPart { | |||
protected byte[] data; | |||
private byte[] data; | |||
/** | |||
* @param path the path to the part, eg Contents or Quill, QuillSub, CONTENTS | |||
*/ | |||
@@ -94,7 +94,11 @@ public abstract class HPBFPart { | |||
* Returns the raw data that makes up | |||
* this document part. | |||
*/ | |||
public byte[] getData() { return data; } | |||
public final byte[] getData() { return data; } | |||
protected final void setData(byte data[]) { | |||
this.data = data.clone(); | |||
} | |||
/** | |||
* Returns |
@@ -39,6 +39,7 @@ public final class QuillContents extends HPBFPart { | |||
// Now parse the first 512 bytes, and produce | |||
// all our bits | |||
byte data[] = getData(); | |||
// Check first 8 bytes | |||
String f8 = new String(data, 0, 8, LocaleUtil.CHARSET_1252); |
@@ -21,15 +21,15 @@ package org.apache.poi.hpbf.model.qcbits; | |||
* Parent of all Quill CONTENTS bits | |||
*/ | |||
public abstract class QCBit { | |||
protected String thingType; | |||
protected String bitType; | |||
protected byte[] data; | |||
private String thingType; | |||
private String bitType; | |||
private byte[] data; | |||
protected int optA; | |||
protected int optB; | |||
protected int optC; | |||
private int optA; | |||
private int optB; | |||
private int optC; | |||
protected int dataOffset; | |||
private int dataOffset; | |||
public QCBit(String thingType, String bitType, byte[] data) { | |||
this.thingType = thingType; | |||
@@ -47,7 +47,10 @@ public abstract class QCBit { | |||
* or PLC | |||
*/ | |||
public String getBitType() { return bitType; } | |||
public byte[] getData() { return data; } | |||
public final byte[] getData() { return data; } | |||
protected final void setData(byte data[]) { | |||
this.data = data.clone(); | |||
} | |||
public int getOptA() { | |||
return optA; |
@@ -26,18 +26,18 @@ import org.apache.poi.util.StringUtil; | |||
* format is determined by the type of the PLCs. | |||
*/ | |||
public abstract class QCPLCBit extends QCBit { | |||
protected int numberOfPLCs; | |||
protected int typeOfPLCS; | |||
private int numberOfPLCs; | |||
private int typeOfPLCS; | |||
/** | |||
* The data which goes before the main PLC entries. | |||
* This is apparently always made up of 2 byte | |||
* un-signed ints.. | |||
*/ | |||
protected int[] preData; | |||
private int[] preData; | |||
/** The first value of each PLC, normally 4 bytes */ | |||
protected long[] plcValA; | |||
private long[] plcValA; | |||
/** The second value of each PLC, normally 4 bytes */ | |||
protected long[] plcValB; | |||
private long[] plcValB; | |||
private QCPLCBit(String thingType, String bitType, byte[] data) { | |||
@@ -74,7 +74,19 @@ public abstract class QCPLCBit extends QCBit { | |||
return plcValB; | |||
} | |||
final void setPreData(int preData[]) { | |||
this.preData = preData.clone(); | |||
} | |||
final void setPlcValA(long[] plcValA) { | |||
this.plcValA = plcValA.clone(); | |||
} | |||
final void setPlcValB(long[] plcValB) { | |||
this.plcValB = plcValB.clone(); | |||
} | |||
public static QCPLCBit createQCPLCBit(String thingType, String bitType, byte[] data) { | |||
// Grab the type | |||
@@ -103,17 +115,23 @@ public abstract class QCPLCBit extends QCBit { | |||
super(thingType, bitType, data); | |||
// Grab our 4x pre-data | |||
preData = new int[4]; | |||
preData[0] = LittleEndian.getUShort(data, 8+0); | |||
preData[1] = LittleEndian.getUShort(data, 8+2); | |||
preData[2] = LittleEndian.getUShort(data, 8+4); | |||
preData[3] = LittleEndian.getUShort(data, 8+6); | |||
// And grab the 2 byte values | |||
for(int i=0; i<numberOfPLCs; i++) { | |||
int preData[] = { | |||
LittleEndian.getUShort(data, 8+0), | |||
LittleEndian.getUShort(data, 8+2), | |||
LittleEndian.getUShort(data, 8+4), | |||
LittleEndian.getUShort(data, 8+6) | |||
}; | |||
setPreData(preData); | |||
// And grab the 2 byte values | |||
int cntPlcs = getNumberOfPLCs(); | |||
long plcValA[] = new long[cntPlcs], plcValB[] = new long[cntPlcs]; | |||
for(int i=0; i<cntPlcs; i++) { | |||
plcValA[i] = LittleEndian.getUShort(data, 16+(4*i)); | |||
plcValB[i] = LittleEndian.getUShort(data, 16+(4*i)+2); | |||
} | |||
setPlcValA(plcValA); | |||
setPlcValB(plcValB); | |||
} | |||
} | |||
@@ -126,17 +144,23 @@ public abstract class QCPLCBit extends QCBit { | |||
super(thingType, bitType, data); | |||
// Grab our 4x pre-data | |||
preData = new int[4]; | |||
preData[0] = LittleEndian.getUShort(data, 8+0); | |||
preData[1] = LittleEndian.getUShort(data, 8+2); | |||
preData[2] = LittleEndian.getUShort(data, 8+4); | |||
preData[3] = LittleEndian.getUShort(data, 8+6); | |||
int preData[] = { | |||
LittleEndian.getUShort(data, 8+0), | |||
LittleEndian.getUShort(data, 8+2), | |||
LittleEndian.getUShort(data, 8+4), | |||
LittleEndian.getUShort(data, 8+6) | |||
}; | |||
setPreData(preData); | |||
// And grab the 4 byte values | |||
for(int i=0; i<numberOfPLCs; i++) { | |||
int cntPlcs = getNumberOfPLCs(); | |||
long plcValA[] = new long[cntPlcs], plcValB[] = new long[cntPlcs]; | |||
for(int i=0; i<cntPlcs; i++) { | |||
plcValA[i] = LittleEndian.getUInt(data, 16+(8*i)); | |||
plcValB[i] = LittleEndian.getUInt(data, 16+(8*i)+4); | |||
} | |||
setPlcValA(plcValA); | |||
setPlcValB(plcValB); | |||
} | |||
} | |||
@@ -149,20 +173,26 @@ public abstract class QCPLCBit extends QCBit { | |||
super(thingType, bitType, data); | |||
// Grab our 7x pre-data | |||
preData = new int[7]; | |||
preData[0] = LittleEndian.getUShort(data, 8+0); | |||
preData[1] = LittleEndian.getUShort(data, 8+2); | |||
preData[2] = LittleEndian.getUShort(data, 8+4); | |||
preData[3] = LittleEndian.getUShort(data, 8+6); | |||
preData[4] = LittleEndian.getUShort(data, 8+8); | |||
preData[5] = LittleEndian.getUShort(data, 8+10); | |||
preData[6] = LittleEndian.getUShort(data, 8+12); | |||
int preData[] = { | |||
LittleEndian.getUShort(data, 8+0), | |||
LittleEndian.getUShort(data, 8+2), | |||
LittleEndian.getUShort(data, 8+4), | |||
LittleEndian.getUShort(data, 8+6), | |||
LittleEndian.getUShort(data, 8+8), | |||
LittleEndian.getUShort(data, 8+10), | |||
LittleEndian.getUShort(data, 8+12) | |||
}; | |||
setPreData(preData); | |||
// And grab the 4 byte values | |||
for(int i=0; i<numberOfPLCs; i++) { | |||
int cntPlcs = getNumberOfPLCs(); | |||
long plcValA[] = new long[cntPlcs], plcValB[] = new long[cntPlcs]; | |||
for(int i=0; i<cntPlcs; i++) { | |||
plcValA[i] = LittleEndian.getUInt(data, 22+(8*i)); | |||
plcValB[i] = LittleEndian.getUInt(data, 22+(8*i)+4); | |||
} | |||
setPlcValA(plcValA); | |||
setPlcValB(plcValB); | |||
} | |||
} | |||
@@ -183,37 +213,38 @@ public abstract class QCPLCBit extends QCBit { | |||
private Type12(String thingType, String bitType, byte[] data) { | |||
super(thingType, bitType, data); | |||
int cntPlcs = getNumberOfPLCs(); | |||
// How many hyperlinks do we really have? | |||
// (zero hyperlinks gets numberOfPLCs=1) | |||
if(data.length == 0x34) { | |||
hyperlinks = new String[0]; | |||
} else { | |||
hyperlinks = new String[numberOfPLCs]; | |||
} | |||
hyperlinks = new String[data.length == 0x34 ? 0 : cntPlcs]; | |||
// We have 4 bytes, then the start point of each | |||
// hyperlink, then the end point of the text. | |||
preData = new int[1+numberOfPLCs+1]; | |||
int preData[] = new int[1+cntPlcs+1]; | |||
for(int i=0; i<preData.length; i++) { | |||
preData[i] = (int)LittleEndian.getUInt(data, 8+(i*4)); | |||
} | |||
setPreData(preData); | |||
// Then we have a whole bunch of stuff, which grows | |||
// with the number of hyperlinks | |||
// For now, we think these are shorts | |||
int at = 8+4+(numberOfPLCs*4)+4; | |||
int at = 8+4+(cntPlcs*4)+4; | |||
int until = 0x34; | |||
if(numberOfPLCs == 1 && hyperlinks.length == 1) { | |||
if(cntPlcs == 1 && hyperlinks.length == 1) { | |||
until = oneStartsAt; | |||
} else if(numberOfPLCs >= 2) { | |||
until = twoStartsAt + (numberOfPLCs-2)*threePlusIncrement; | |||
} else if(cntPlcs >= 2) { | |||
until = twoStartsAt + (cntPlcs-2)*threePlusIncrement; | |||
} | |||
plcValA = new long[(until-at)/2]; | |||
plcValB = new long[0]; | |||
long plcValA[] = new long[(until-at)/2]; | |||
long plcValB[] = new long[0]; | |||
for(int i=0; i<plcValA.length; i++) { | |||
plcValA[i] = LittleEndian.getUShort(data, at+(i*2)); | |||
} | |||
setPlcValA(plcValA); | |||
setPlcValB(plcValB); | |||
// Finally, we have a series of lengths + hyperlinks | |||
at = until; | |||
@@ -259,7 +290,7 @@ public abstract class QCPLCBit extends QCBit { | |||
* @param number The hyperlink number, zero based | |||
*/ | |||
public int getTextStartAt(int number) { | |||
return preData[1+number]; | |||
return getPreData()[1+number]; | |||
} | |||
/** | |||
* Returns where in the text that this block | |||
@@ -269,7 +300,7 @@ public abstract class QCPLCBit extends QCBit { | |||
* PLCBit applies to. | |||
*/ | |||
public int getAllTextEndAt() { | |||
return preData[numberOfPLCs+1]; | |||
return getPreData()[getNumberOfPLCs()+1]; | |||
} | |||
} | |||
} |
@@ -32,13 +32,12 @@ public final class QCTextBit extends QCBit { | |||
* are \r and not \n | |||
*/ | |||
public String getText() { | |||
return StringUtil.getFromUnicodeLE( | |||
data, 0, data.length/2 | |||
); | |||
return StringUtil.getFromUnicodeLE(getData()); | |||
} | |||
public void setText(String text) { | |||
data = new byte[text.length()*2]; | |||
byte data[] = new byte[text.length()*2]; | |||
StringUtil.putUnicodeLE(text, data, 0); | |||
setData(data); | |||
} | |||
} |
@@ -28,10 +28,10 @@ public abstract class StreamTest extends TestCase { | |||
protected boolean hasPointers = false; | |||
public TestPointer(boolean compressed, int offset, int length, int type, short format) { | |||
this.compressed = compressed; | |||
this.offset = offset; | |||
this.length = length; | |||
this.type = type; | |||
this.format = format; | |||
setOffset(offset); | |||
setLength(length); | |||
setType(type); | |||
setFormat(format); | |||
} | |||
@Override |