git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1771583 13f79535-47bb-0310-9956-ffa450edef68tags/REL_3_16_BETA2
package org.apache.poi.hpsf; | package org.apache.poi.hpsf; | ||||
import java.util.Arrays; | |||||
import org.apache.poi.util.HexDump; | import org.apache.poi.util.HexDump; | ||||
import org.apache.poi.util.StringUtil; | import org.apache.poi.util.StringUtil; | ||||
public static final ClassID POWERPOINT95 = new ClassID("{EA7BAE70-FB3B-11CD-A903-00AA00510EA3}"); | public static final ClassID POWERPOINT95 = new ClassID("{EA7BAE70-FB3B-11CD-A903-00AA00510EA3}"); | ||||
public static final ClassID EQUATION30 = new ClassID("{0002CE02-0000-0000-C000-000000000046}"); | 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, | * <p>The bytes making out the class ID in correct order, | ||||
* i.e. big-endian.</p> | * i.e. big-endian.</p> | ||||
*/ | */ | ||||
protected byte[] bytes; | |||||
private final byte[] bytes = new byte[LENGTH]; | |||||
* @param src The byte array to read from. | * @param src The byte array to read from. | ||||
* @param offset The offset of the first byte to read. | * @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); | read(src, offset); | ||||
} | } | ||||
* <p>Creates a {@link ClassID} and initializes its value with | * <p>Creates a {@link ClassID} and initializes its value with | ||||
* 0x00 bytes.</p> | * 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); | |||||
} | } | ||||
* @param externalForm representation of the Class ID represented by this object. | * @param externalForm representation of the Class ID represented by this object. | ||||
*/ | */ | ||||
public ClassID(String externalForm) { | public ClassID(String externalForm) { | ||||
bytes = new byte[LENGTH]; | |||||
String clsStr = externalForm.replaceAll("[{}-]", ""); | String clsStr = externalForm.replaceAll("[{}-]", ""); | ||||
for (int i=0; i<clsStr.length(); i+=2) { | for (int i=0; i<clsStr.length(); i+=2) { | ||||
bytes[i/2] = (byte)Integer.parseInt(clsStr.substring(i, i+2), 16); | bytes[i/2] = (byte)Integer.parseInt(clsStr.substring(i, i+2), 16); | ||||
} | } | ||||
/** <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 | * @return The number of bytes occupied by this object in the byte | ||||
* stream. | * stream. | ||||
*/ | */ | ||||
public int length() | |||||
{ | |||||
public int length() { | |||||
return LENGTH; | return LENGTH; | ||||
} | } | ||||
* | * | ||||
* @return the bytes making out the class ID. | * @return the bytes making out the class ID. | ||||
*/ | */ | ||||
public byte[] getBytes() | |||||
{ | |||||
public byte[] getBytes() { | |||||
return bytes; | return bytes; | ||||
} | } | ||||
* @param bytes The bytes making out the class ID in big-endian format. They | * @param bytes The bytes making out the class ID in big-endian format. They | ||||
* are copied without their order being changed. | * 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); | |||||
} | } | ||||
* | * | ||||
* @return A byte array containing the class ID. | * @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. */ | /* Read double word. */ | ||||
bytes[0] = src[3 + offset]; | bytes[0] = src[3 + offset]; | ||||
bytes[1] = src[2 + offset]; | bytes[1] = src[2 + offset]; | ||||
bytes[7] = src[6 + offset]; | bytes[7] = src[6 + offset]; | ||||
/* Read 8 bytes. */ | /* 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; | return bytes; | ||||
} | } | ||||
* ID 16 bytes in the byte array after the <var>offset</var> position. | * ID 16 bytes in the byte array after the <var>offset</var> position. | ||||
*/ | */ | ||||
public void write(final byte[] dst, final int offset) | public void write(final byte[] dst, final int offset) | ||||
throws ArrayStoreException | |||||
{ | |||||
throws ArrayStoreException { | |||||
/* Check array size: */ | /* Check array size: */ | ||||
if (dst.length < 16) | |||||
if (dst.length < LENGTH) { | |||||
throw new ArrayStoreException | throw new ArrayStoreException | ||||
("Destination byte[] must have room for at least 16 bytes, " + | ("Destination byte[] must have room for at least 16 bytes, " + | ||||
"but has a length of only " + dst.length + "."); | "but has a length of only " + dst.length + "."); | ||||
} | |||||
/* Write double word. */ | /* Write double word. */ | ||||
dst[0 + offset] = bytes[3]; | dst[0 + offset] = bytes[3]; | ||||
dst[1 + offset] = bytes[2]; | dst[1 + offset] = bytes[2]; | ||||
dst[7 + offset] = bytes[6]; | dst[7 + offset] = bytes[6]; | ||||
/* Write 8 bytes. */ | /* Write 8 bytes. */ | ||||
for (int i = 8; i < 16; i++) | |||||
dst[i + offset] = bytes[i]; | |||||
System.arraycopy(bytes, 8, dst, 8 + offset, 8); | |||||
} | } | ||||
* <code>false</code>. | * <code>false</code>. | ||||
*/ | */ | ||||
@Override | @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; | return false; | ||||
} | |||||
final ClassID cid = (ClassID) o; | final ClassID cid = (ClassID) o; | ||||
if (bytes.length != cid.bytes.length) | |||||
if (bytes.length != cid.bytes.length) { | |||||
return false; | 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 false; | ||||
} | |||||
} | |||||
return true; | return true; | ||||
} | } | ||||
* @see Object#hashCode() | * @see Object#hashCode() | ||||
*/ | */ | ||||
@Override | @Override | ||||
public int hashCode() | |||||
{ | |||||
public int hashCode() { | |||||
return new String(bytes, StringUtil.UTF8).hashCode(); | return new String(bytes, StringUtil.UTF8).hashCode(); | ||||
} | } | ||||
* @return String representation of the Class ID represented by this object. | * @return String representation of the Class ID represented by this object. | ||||
*/ | */ | ||||
@Override | @Override | ||||
public String toString() | |||||
{ | |||||
StringBuffer sbClassId = new StringBuffer(38); | |||||
public String toString() { | |||||
StringBuilder sbClassId = new StringBuilder(38); | |||||
sbClassId.append('{'); | sbClassId.append('{'); | ||||
for (int i = 0; i < 16; i++) | |||||
{ | |||||
for (int i = 0; i < LENGTH; i++) { | |||||
sbClassId.append(HexDump.toHex(bytes[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('-'); | ||||
} | |||||
} | } | ||||
sbClassId.append('}'); | sbClassId.append('}'); | ||||
return sbClassId.toString(); | return sbClassId.toString(); | ||||
} | } | ||||
} | } |
* TODO Fix this, as it starts to go wrong on | * TODO Fix this, as it starts to go wrong on | ||||
* large streams | * large streams | ||||
*/ | */ | ||||
final class HDGFLZWCompressor { | |||||
/* package */ final class HDGFLZWCompressor { | |||||
// We use 12 bit codes: | // We use 12 bit codes: | ||||
// * 0-255 are real bytes | // * 0-255 are real bytes | ||||
// * 256-4095 are the substring codes | // * 256-4095 are the substring codes | ||||
// Java handily initialises our buffer / dictionary | // Java handily initialises our buffer / dictionary | ||||
// to all zeros | // to all zeros | ||||
byte[] dict = new byte[4096]; | |||||
private byte[] dict = new byte[4096]; | |||||
// The next block of data to be written out, minus | // The next block of data to be written out, minus | ||||
// its mask byte | // its mask byte | ||||
byte[] buffer = new byte[16]; | |||||
private byte[] buffer = new byte[16]; | |||||
// And how long it is | // And how long it is | ||||
// (Un-compressed codes are 1 byte each, compressed codes | // (Un-compressed codes are 1 byte each, compressed codes | ||||
// are two) | // are two) | ||||
int bufferLen = 0; | |||||
private int bufferLen = 0; | |||||
// The raw length of a code is limited to 4 bits + 2 | // 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 | // And how much we're using | ||||
int rawCodeLen = 0; | |||||
private int rawCodeLen = 0; | |||||
// How far through the input and output streams we are | // 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 | // What the next mask byte to output will be | ||||
int nextMask = 0; | |||||
private int nextMask = 0; | |||||
// And how many bits we've already set | // And how many bits we've already set | ||||
int maskBitsSet = 0; | |||||
private int maskBitsSet = 0; | |||||
public HDGFLZWCompressor() {} | public HDGFLZWCompressor() {} | ||||
public int getOnDiskSize() { | public int getOnDiskSize() { | ||||
int size = header.getSizeInBytes() + contents.length; | int size = header.getSizeInBytes() + contents.length; | ||||
if(trailer != null) { | if(trailer != null) { | ||||
size += trailer.trailerData.length; | |||||
size += trailer.getTrailerData().length; | |||||
} | } | ||||
if(separator != null) { | if(separator != null) { | ||||
size += separator.separatorData.length; | size += separator.separatorData.length; |
ChunkHeader header = | ChunkHeader header = | ||||
ChunkHeader.createChunkHeader(version, data, offset); | ChunkHeader.createChunkHeader(version, data, offset); | ||||
// Sanity check | // Sanity check | ||||
if(header.length < 0) { | |||||
if(header.getLength() < 0) { | |||||
throw new IllegalArgumentException("Found a chunk with a negative length, which isn't allowed"); | throw new IllegalArgumentException("Found a chunk with a negative length, which isn't allowed"); | ||||
} | } | ||||
"Header called for " + header.getLength() +" bytes, but that would take us past the end of the data!"); | "Header called for " + header.getLength() +" bytes, but that would take us past the end of the data!"); | ||||
endOfDataPos = data.length; | endOfDataPos = data.length; | ||||
header.length = data.length - offset - header.getSizeInBytes(); | |||||
header.setLength(data.length - offset - header.getSizeInBytes()); | |||||
if(header.hasTrailer()) { | if(header.hasTrailer()) { | ||||
header.length -= 8; | |||||
header.setLength(header.getLength() - 8); | |||||
endOfDataPos -= 8; | endOfDataPos -= 8; | ||||
} | } | ||||
if(header.hasSeparator()) { | if(header.hasSeparator()) { | ||||
header.length -= 4; | |||||
header.setLength(header.getLength() - 4); | |||||
endOfDataPos -= 4; | endOfDataPos -= 4; | ||||
} | } | ||||
} | } |
* A chunk header | * A chunk header | ||||
*/ | */ | ||||
public abstract class ChunkHeader { | 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 | * Creates the appropriate ChunkHeader for the Chunk Header at | ||||
} else { | } else { | ||||
ch = new ChunkHeaderV6(); | 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; | return ch; | ||||
} else if(documentVersion == 5 || documentVersion == 4) { | } else if(documentVersion == 5 || documentVersion == 4) { | ||||
ChunkHeaderV4V5 ch = new ChunkHeaderV4V5(); | 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; | return ch; | ||||
} else { | } else { | ||||
public int getId() { | public int getId() { | ||||
return id; | return id; | ||||
} | } | ||||
/** | /** | ||||
* Returns the length of the trunk, excluding the length | * Returns the length of the trunk, excluding the length | ||||
* of the header, trailer or separator. | * of the header, trailer or separator. | ||||
public int getLength() { | public int getLength() { | ||||
return length; | return length; | ||||
} | } | ||||
/** | /** | ||||
* Returns the type of the chunk, which affects the | * Returns the type of the chunk, which affects the | ||||
* mandatory information | * mandatory information | ||||
public int getType() { | public int getType() { | ||||
return type; | return type; | ||||
} | } | ||||
public int getUnknown1() { | public int getUnknown1() { | ||||
return unknown1; | 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; | |||||
} | |||||
} | } |
* A chunk header from v11+ | * A chunk header from v11+ | ||||
*/ | */ | ||||
public final class ChunkHeaderV11 extends ChunkHeaderV6 { | 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"); | |||||
} | |||||
} | } |
* A chunk header from v4 or v5 | * A chunk header from v4 or v5 | ||||
*/ | */ | ||||
public final class ChunkHeaderV4V5 extends ChunkHeader { | public final class ChunkHeaderV4V5 extends ChunkHeader { | ||||
protected short unknown2; | |||||
protected short unknown3; | |||||
private short unknown2; | |||||
private short unknown3; | |||||
public short getUnknown2() { | public short getUnknown2() { | ||||
return unknown2; | return unknown2; | ||||
public Charset getChunkCharset() { | public Charset getChunkCharset() { | ||||
return Charset.forName("ASCII"); | return Charset.forName("ASCII"); | ||||
} | } | ||||
void setUnknown2(short unknown2) { | |||||
this.unknown2 = unknown2; | |||||
} | |||||
void setUnknown3(short unknown3) { | |||||
this.unknown3 = unknown3; | |||||
} | |||||
} | } |
* A chunk header from v6 | * A chunk header from v6 | ||||
*/ | */ | ||||
public class ChunkHeaderV6 extends ChunkHeader { | public class ChunkHeaderV6 extends ChunkHeader { | ||||
protected short unknown2; | |||||
protected short unknown3; | |||||
private short unknown2; | |||||
private short unknown3; | |||||
public short getUnknown2() { | public short getUnknown2() { | ||||
return unknown2; | return unknown2; | ||||
* Does the chunk have a trailer? | * Does the chunk have a trailer? | ||||
*/ | */ | ||||
public boolean hasTrailer() { | 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? | * Does the chunk have a separator? | ||||
*/ | */ | ||||
public Charset getChunkCharset() { | public Charset getChunkCharset() { | ||||
return Charset.forName("ASCII"); | return Charset.forName("ASCII"); | ||||
} | } | ||||
void setUnknown2(short unknown2) { | |||||
this.unknown2 = unknown2; | |||||
} | |||||
void setUnknown3(short unknown3) { | |||||
this.unknown3 = unknown3; | |||||
} | |||||
} | } |
* A trailer that follows a chunk | * A trailer that follows a chunk | ||||
*/ | */ | ||||
public final class ChunkTrailer { | public final class ChunkTrailer { | ||||
protected byte[] trailerData; | |||||
private byte[] trailerData; | |||||
public ChunkTrailer(byte[] data, int offset) { | public ChunkTrailer(byte[] data, int offset) { | ||||
trailerData = new byte[8]; | trailerData = new byte[8]; | ||||
public String toString() { | public String toString() { | ||||
return "<ChunkTrailer of length " + trailerData.length + ">"; | return "<ChunkTrailer of length " + trailerData.length + ">"; | ||||
} | } | ||||
byte[] getTrailerData() { | |||||
return trailerData; | |||||
} | |||||
} | } |
public final class VSDDumper { | 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 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) { | VSDDumper(PrintStream ps, HDGFDiagram hdgf) { | ||||
this.ps = ps; | this.ps = ps; | ||||
this.hdgf = hdgf; | this.hdgf = hdgf; |
* blocks elsewhere in the file | * blocks elsewhere in the file | ||||
*/ | */ | ||||
public abstract class Pointer { | 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() { | public int getAddress() { | ||||
return address; | return address; | ||||
public abstract boolean destinationHasPointers(); | public abstract boolean destinationHasPointers(); | ||||
public abstract boolean destinationHasChunks(); | public abstract boolean destinationHasChunks(); | ||||
public abstract boolean destinationCompressed(); | 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); | |||||
} | |||||
} | } |
Pointer p; | Pointer p; | ||||
if(version >= 6) { | if(version >= 6) { | ||||
p = new PointerV6(); | 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; | return p; | ||||
} else if(version == 5) { | } else if(version == 5) { | ||||
p = new PointerV5(); | 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; | return p; | ||||
} else { | } else { |
public final class PointerV5 extends Pointer { | public final class PointerV5 extends Pointer { | ||||
// TODO Are these getters correct? | // TODO Are these getters correct? | ||||
public boolean destinationHasStrings() { | public boolean destinationHasStrings() { | ||||
return (0x40 <= format && format < 0x50); | |||||
return isFormatBetween(0x40, 0x50); | |||||
} | } | ||||
public boolean destinationHasPointers() { | 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() { | 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() { | public boolean destinationCompressed() { | ||||
// Apparently, it's the second least significant bit | // Apparently, it's the second least significant bit | ||||
return (format & 2) > 0; | |||||
return (getFormat() & 2) > 0; | |||||
} | } | ||||
/** | /** | ||||
* Depends on the type only, not stored | * Depends on the type only, not stored | ||||
*/ | */ | ||||
public int getNumPointersOffset(byte[] data) { | public int getNumPointersOffset(byte[] data) { | ||||
switch (type) { | |||||
switch (getType()) { | |||||
case 0x1d: | case 0x1d: | ||||
case 0x4e: | case 0x4e: | ||||
return 30; | return 30; |
*/ | */ | ||||
public final class PointerV6 extends Pointer { | public final class PointerV6 extends Pointer { | ||||
public boolean destinationHasStrings() { | public boolean destinationHasStrings() { | ||||
return (0x40 <= format && format < 0x50); | |||||
return isFormatBetween(0x40, 0x50); | |||||
} | } | ||||
public boolean destinationHasPointers() { | 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() { | public boolean destinationHasChunks() { | ||||
return (0xd0 <= format && format < 0xdf); | |||||
return isFormatBetween(0xd0, 0xdf); | |||||
} | } | ||||
public boolean destinationCompressed() { | public boolean destinationCompressed() { | ||||
// Apparently, it's the second least significant bit | // Apparently, it's the second least significant bit | ||||
return (format & 2) > 0; | |||||
return (getFormat() & 2) > 0; | |||||
} | } | ||||
/** | /** |
new DefaultEscherRecordFactory(); | new DefaultEscherRecordFactory(); | ||||
ArrayList<EscherRecord> ec = new ArrayList<EscherRecord>(); | ArrayList<EscherRecord> ec = new ArrayList<EscherRecord>(); | ||||
byte data[] = getData(); | |||||
int left = data.length; | int left = data.length; | ||||
while(left > 0) { | while(left > 0) { | ||||
EscherRecord er = erf.createRecord(data, 0); | EscherRecord er = erf.createRecord(data, 0); | ||||
size += records[i].getRecordSize(); | size += records[i].getRecordSize(); | ||||
} | } | ||||
data = new byte[size]; | |||||
byte data[] = new byte[size]; | |||||
size = 0; | size = 0; | ||||
for(int i=0; i<records.length; i++) { | for(int i=0; i<records.length; i++) { | ||||
int thisSize = | int thisSize = | ||||
records[i].serialize(size, data); | records[i].serialize(size, data); | ||||
size += thisSize; | size += thisSize; | ||||
} | } | ||||
setData(data); | |||||
} | } | ||||
} | } |
* for all of them. | * for all of them. | ||||
*/ | */ | ||||
public abstract class HPBFPart { | public abstract class HPBFPart { | ||||
protected byte[] data; | |||||
private byte[] data; | |||||
/** | /** | ||||
* @param path the path to the part, eg Contents or Quill, QuillSub, CONTENTS | * @param path the path to the part, eg Contents or Quill, QuillSub, CONTENTS | ||||
*/ | */ | ||||
* Returns the raw data that makes up | * Returns the raw data that makes up | ||||
* this document part. | * 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 | * Returns |
// Now parse the first 512 bytes, and produce | // Now parse the first 512 bytes, and produce | ||||
// all our bits | // all our bits | ||||
byte data[] = getData(); | |||||
// Check first 8 bytes | // Check first 8 bytes | ||||
String f8 = new String(data, 0, 8, LocaleUtil.CHARSET_1252); | String f8 = new String(data, 0, 8, LocaleUtil.CHARSET_1252); |
* Parent of all Quill CONTENTS bits | * Parent of all Quill CONTENTS bits | ||||
*/ | */ | ||||
public abstract class QCBit { | 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) { | public QCBit(String thingType, String bitType, byte[] data) { | ||||
this.thingType = thingType; | this.thingType = thingType; | ||||
* or PLC | * or PLC | ||||
*/ | */ | ||||
public String getBitType() { return bitType; } | 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() { | public int getOptA() { | ||||
return optA; | return optA; |
* format is determined by the type of the PLCs. | * format is determined by the type of the PLCs. | ||||
*/ | */ | ||||
public abstract class QCPLCBit extends QCBit { | 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. | * The data which goes before the main PLC entries. | ||||
* This is apparently always made up of 2 byte | * This is apparently always made up of 2 byte | ||||
* un-signed ints.. | * un-signed ints.. | ||||
*/ | */ | ||||
protected int[] preData; | |||||
private int[] preData; | |||||
/** The first value of each PLC, normally 4 bytes */ | /** The first value of each PLC, normally 4 bytes */ | ||||
protected long[] plcValA; | |||||
private long[] plcValA; | |||||
/** The second value of each PLC, normally 4 bytes */ | /** The second value of each PLC, normally 4 bytes */ | ||||
protected long[] plcValB; | |||||
private long[] plcValB; | |||||
private QCPLCBit(String thingType, String bitType, byte[] data) { | private QCPLCBit(String thingType, String bitType, byte[] data) { | ||||
return plcValB; | 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) { | public static QCPLCBit createQCPLCBit(String thingType, String bitType, byte[] data) { | ||||
// Grab the type | // Grab the type | ||||
super(thingType, bitType, data); | super(thingType, bitType, data); | ||||
// Grab our 4x pre-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)); | plcValA[i] = LittleEndian.getUShort(data, 16+(4*i)); | ||||
plcValB[i] = LittleEndian.getUShort(data, 16+(4*i)+2); | plcValB[i] = LittleEndian.getUShort(data, 16+(4*i)+2); | ||||
} | } | ||||
setPlcValA(plcValA); | |||||
setPlcValB(plcValB); | |||||
} | } | ||||
} | } | ||||
super(thingType, bitType, data); | super(thingType, bitType, data); | ||||
// Grab our 4x pre-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 | // 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)); | plcValA[i] = LittleEndian.getUInt(data, 16+(8*i)); | ||||
plcValB[i] = LittleEndian.getUInt(data, 16+(8*i)+4); | plcValB[i] = LittleEndian.getUInt(data, 16+(8*i)+4); | ||||
} | } | ||||
setPlcValA(plcValA); | |||||
setPlcValB(plcValB); | |||||
} | } | ||||
} | } | ||||
super(thingType, bitType, data); | super(thingType, bitType, data); | ||||
// Grab our 7x pre-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 | // 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)); | plcValA[i] = LittleEndian.getUInt(data, 22+(8*i)); | ||||
plcValB[i] = LittleEndian.getUInt(data, 22+(8*i)+4); | plcValB[i] = LittleEndian.getUInt(data, 22+(8*i)+4); | ||||
} | } | ||||
setPlcValA(plcValA); | |||||
setPlcValB(plcValB); | |||||
} | } | ||||
} | } | ||||
private Type12(String thingType, String bitType, byte[] data) { | private Type12(String thingType, String bitType, byte[] data) { | ||||
super(thingType, bitType, data); | super(thingType, bitType, data); | ||||
int cntPlcs = getNumberOfPLCs(); | |||||
// How many hyperlinks do we really have? | // How many hyperlinks do we really have? | ||||
// (zero hyperlinks gets numberOfPLCs=1) | // (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 | // We have 4 bytes, then the start point of each | ||||
// hyperlink, then the end point of the text. | // 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++) { | for(int i=0; i<preData.length; i++) { | ||||
preData[i] = (int)LittleEndian.getUInt(data, 8+(i*4)); | preData[i] = (int)LittleEndian.getUInt(data, 8+(i*4)); | ||||
} | } | ||||
setPreData(preData); | |||||
// Then we have a whole bunch of stuff, which grows | // Then we have a whole bunch of stuff, which grows | ||||
// with the number of hyperlinks | // with the number of hyperlinks | ||||
// For now, we think these are shorts | // For now, we think these are shorts | ||||
int at = 8+4+(numberOfPLCs*4)+4; | |||||
int at = 8+4+(cntPlcs*4)+4; | |||||
int until = 0x34; | int until = 0x34; | ||||
if(numberOfPLCs == 1 && hyperlinks.length == 1) { | |||||
if(cntPlcs == 1 && hyperlinks.length == 1) { | |||||
until = oneStartsAt; | 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++) { | for(int i=0; i<plcValA.length; i++) { | ||||
plcValA[i] = LittleEndian.getUShort(data, at+(i*2)); | plcValA[i] = LittleEndian.getUShort(data, at+(i*2)); | ||||
} | } | ||||
setPlcValA(plcValA); | |||||
setPlcValB(plcValB); | |||||
// Finally, we have a series of lengths + hyperlinks | // Finally, we have a series of lengths + hyperlinks | ||||
at = until; | at = until; | ||||
* @param number The hyperlink number, zero based | * @param number The hyperlink number, zero based | ||||
*/ | */ | ||||
public int getTextStartAt(int number) { | public int getTextStartAt(int number) { | ||||
return preData[1+number]; | |||||
return getPreData()[1+number]; | |||||
} | } | ||||
/** | /** | ||||
* Returns where in the text that this block | * Returns where in the text that this block | ||||
* PLCBit applies to. | * PLCBit applies to. | ||||
*/ | */ | ||||
public int getAllTextEndAt() { | public int getAllTextEndAt() { | ||||
return preData[numberOfPLCs+1]; | |||||
return getPreData()[getNumberOfPLCs()+1]; | |||||
} | } | ||||
} | } | ||||
} | } |
* are \r and not \n | * are \r and not \n | ||||
*/ | */ | ||||
public String getText() { | public String getText() { | ||||
return StringUtil.getFromUnicodeLE( | |||||
data, 0, data.length/2 | |||||
); | |||||
return StringUtil.getFromUnicodeLE(getData()); | |||||
} | } | ||||
public void setText(String text) { | public void setText(String text) { | ||||
data = new byte[text.length()*2]; | |||||
byte data[] = new byte[text.length()*2]; | |||||
StringUtil.putUnicodeLE(text, data, 0); | StringUtil.putUnicodeLE(text, data, 0); | ||||
setData(data); | |||||
} | } | ||||
} | } |
protected boolean hasPointers = false; | protected boolean hasPointers = false; | ||||
public TestPointer(boolean compressed, int offset, int length, int type, short format) { | public TestPointer(boolean compressed, int offset, int length, int type, short format) { | ||||
this.compressed = compressed; | this.compressed = compressed; | ||||
this.offset = offset; | |||||
this.length = length; | |||||
this.type = type; | |||||
this.format = format; | |||||
setOffset(offset); | |||||
setLength(length); | |||||
setType(type); | |||||
setFormat(format); | |||||
} | } | ||||
@Override | @Override |